• R/O
  • SSH
  • HTTPS

yash: Commit


Commit MetaInfo

Revision3889 (tree)
Time2018-09-23 13:36:18
Authormagicant

Log Message

Merge token_based_parser branch

This commit merges a massive refactoring in the parser.

The parser is now implemented in a token-based algorithm. The entire
parser has been split into the lexical tokenizer and the LL(1) syntax
parser. With the new parser architecture, it would be possible to
implement a parser for the [[ command.

Change Summary

Incremental Difference

--- yash/trunk/alias.c (revision 3888)
+++ yash/trunk/alias.c (revision 3889)
@@ -67,6 +67,8 @@
6767 * substitution after another substitution that ends with a blank.
6868 * Alias list items are sorted in the order of `limitindex'. */
6969
70+static bool is_alias_name_char(wchar_t c)
71+ __attribute__((pure));
7072 static void free_alias(alias_T *alias);
7173 static inline void vfreealias(kvpair_T kv);
7274 static void define_alias(
@@ -103,7 +105,7 @@
103105 }
104106
105107 /* Returns true iff `c' is a character that can be used in an alias name. */
106-inline bool is_alias_name_char(wchar_t c)
108+bool is_alias_name_char(wchar_t c)
107109 {
108110 return !wcschr(L" \t\n=$<>\\'\"`;&|()#", c) && !iswblank(c);
109111 }
@@ -282,9 +284,14 @@
282284 bool substitute_alias(xwcsbuf_T *restrict buf, size_t i,
283285 aliaslist_T **restrict list, substaliasflags_T flags)
284286 {
287+ if (is_redir_fd(&buf->contents[i]))
288+ return false;
289+
285290 size_t j = i;
286291 while (is_alias_name_char(buf->contents[j]))
287292 j++;
293+ if (!is_token_delimiter_char(buf->contents[j]))
294+ return false;
288295 return substitute_alias_range(buf, i, j, list, flags);
289296 }
290297
@@ -307,16 +314,11 @@
307314 if (!(flags & AF_NONGLOBAL) && posixly_correct)
308315 return false;
309316
310- /* check if there is an alias name */
311317 if (i >= j)
312318 return false;
313319 if (flags & AF_NOEOF)
314320 if (j == buf->length)
315321 return false;
316- if (!is_token_delimiter_char(buf->contents[j]))
317- return false;
318- if (is_redir_fd(buf->contents + i))
319- return false;
320322
321323 alias_T *alias;
322324
--- yash/trunk/alias.h (revision 3888)
+++ yash/trunk/alias.h (revision 3889)
@@ -1,6 +1,6 @@
11 /* Yash: yet another shell */
22 /* alias.h: alias substitution */
3-/* (C) 2007-2017 magicant */
3+/* (C) 2007-2018 magicant */
44
55 /* This program is free software: you can redistribute it and/or modify
66 * it under the terms of the GNU General Public License as published by
@@ -32,8 +32,6 @@
3232 } substaliasflags_T;
3333
3434 extern void init_alias(void);
35-extern _Bool is_alias_name_char(wchar_t c)
36- __attribute__((pure));
3735 extern const wchar_t *get_alias_value(const wchar_t *aliasname)
3836 __attribute__((nonnull,pure));
3937 extern void destroy_aliaslist(struct aliaslist_T *list);
--- yash/trunk/doc/fgrammar.txt (revision 3888)
+++ yash/trunk/doc/fgrammar.txt (revision 3889)
@@ -4,102 +4,34 @@
44 //:title: Yash manual - Formal definition of command syntax
55 :description: This page gives the formal definition of yash command syntax.
66
7-This chapter defines the syntax of shell commands as a parsing expression
8-grammar.
7+This chapter defines the syntax of the shell command language.
98
10-The set of terminals of the grammar is the set of characters that can
11-be handled on the environment in which the shell is run (a.k.a. execution
12-character set), with the exception that the set does not contain the null
13-character (`'\0'`).
9+[NOTE]
10+Some of the syntactic features described below are not supported in the
11+link:posix.html[POSIXly-correct mode].
1412
15-Below is a list of nonterminals of the grammar with corresponding parsing
16-expressions.
17-The list does not include rules for parsing contents and ends of
18-link:redir.html#here[here documents].
19-In the link:posix.html[POSIXly-correct mode], the grammar varies from the list
20-below to disable non-POSIX functionalities.
13+[[token]]
14+== Tokenization
2115
22-[[d-complete-command]]CompleteCommand::
23-<<d-sequence,Sequence>> <<d-eof,EOF>>
16+The characters of the input source code are first delimited into tokens.
17+Tokens are delimited so that the earlier token spans as long as possible.
18+A sequence of one or more unquoted blank characters delimits a token.
2419
25-[[d-sequence]]Sequence::
26-<<d-n,N>>* <<d-list,List>>*
20+The following tokens are the operator tokens:
2721
28-[[d-list]]List::
29-<<d-pipeline,Pipeline>> ((+&&+ / +||+) <<d-n,N>>* Pipeline)*
30-<<d-list-separator,ListSeparator>>
22+`&` `&&` `(` `)` `;` `;;` `|` `||`
23+`<` `<<` `<&` `<(` `<<-` `<<<` `<>`
24+`>` `>>` `>&` `>(` `>>|` `>|` (newline)
3125
32-[[d-pipeline]]Pipeline::
33-<<d-bang,Bang>>? <<d-command,Command>> (+|+ <<d-n,N>>* Command)*
26+[NOTE]
27+Unlike other programming languages, the newline operator is a token rather
28+than a white space.
3429
35-[[d-command]]Command::
36-<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>* / +
37-!<<d-r,R>> <<d-function-definition,FunctionDefinition>> / +
38-!R <<d-simple-command,SimpleCommand>>
30+Characters that are not blank nor part of an operator compose a word token.
31+Words are parsed by the following parsing expression grammar:
3932
40-[[d-compound-command]]CompoundCommand::
41-<<d-subshell,Subshell>> / +
42-<<d-grouping,Grouping>> / +
43-<<d-if-command,IfCommand>> / +
44-<<d-for-command,ForCommand>> / +
45-<<d-while-command,WhileCommand>> / +
46-<<d-case-command,CaseCommand>> / +
47-<<d-function-command,FunctionCommand>>
48-
49-[[d-subshell]]Subshell::
50-+(+ <<d-sequence,Sequence>> +)+ <<d-s,S>>*
51-
52-[[d-grouping]]Grouping::
53-<<d-left-brace,LeftBrace>> <<d-sequence,Sequence>>
54-<<d-right-brace,RightBrace>>
55-
56-[[d-if-command]]IfCommand::
57-<<d-if,If>> <<d-sequence,Sequence>> <<d-then,Then>> Sequence
58-(<<d-elif,Elif>> Sequence <<d-then,Then>> Sequence)*
59-(<<d-else,Else>> Sequence)? <<d-fi,Fi>>
60-
61-[[d-for-command]]ForCommand::
62-<<d-for,For>> <<d-name,Name>> <<d-s,S>>* <<d-separator,Separator>>?
63-(<<d-in,In>> <<d-word,Word>>* <<d-separator,Separator>>)?
64-<<d-do,Do>> <<d-sequence,Sequence>> <<d-done,Done>>
65-
66-[[d-while-command]]WhileCommand::
67-(<<d-while,While>> / <<d-until,Until>>)
68-<<d-sequence,Sequence>> <<d-do,Do>> Sequence <<d-done,Done>>
69-
70-[[d-case-command]]CaseCommand::
71-<<d-case,Case>> <<d-word,Word>> <<d-n,N>>* <<d-in,In>> N*
72-<<d-case-item,CaseItem>>* <<d-esac,Esac>>
73-
74-[[d-case-item]]CaseItem::
75-!<<d-esac,Esac>> (+(+ <<d-s,S>>*)? <<d-word,Word>> (+|+ S* Word)* +)+
76-<<d-sequence,Sequence>> (+;;+ / &amp;Esac)
77-
78-[[d-function-command]]FunctionCommand::
79-<<d-function,Function>> <<d-word,Word>> (+(+ <<d-s,S>>* +)+)? <<d-n,N>>*
80-<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>*
81-
82-[[d-function-definition]]FunctionDefinition::
83-<<d-name,Name>> <<d-s,S>>* +(+ S* +)+ <<d-n,N>>*
84-<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>*
85-
86-[[d-simple-command]]SimpleCommand::
87-&(<<d-word,Word>> / <<d-redirection,Redirection>>)
88-(<<d-assignment,Assignment>> / Redirection)* (Word / Redirection)*
89-
90-[[d-assignment]]Assignment::
91-<<d-name,Name>> +=+ <<d-word,Word>> / +
92-Name +=(+ <<d-n,N>>* (Word N*)* +)+
93-
94-[[d-name]]Name::
95-!\[[:digit:]] [[:alnum:] +_+]+
96-
97-[[d-portable-name]]PortableName::
98-![++0++-++9++] [++0++-++9++ ++ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_++]+
99-
10033 [[d-word]]Word::
10134 (<<d-word-element,WordElement>> / !<<d-special-char,SpecialChar>> .)+
102-<<d-s,S>>*
10335
10436 [[d-word-element]]WordElement::
10537 +\+ . / +
@@ -110,10 +42,10 @@
11042 <<d-command-substitution,CommandSubstitution>>
11143
11244 [[d-quote-element]]QuoteElement::
113-+\+ ([+$&#96;"&#92;+] / <<d-nl,NL>>) / +
45++\+ ([+$&#96;"&#92;+] / <newline>) / +
11446 <<d-parameter,Parameter>> / +
11547 <<d-arithmetic,Arithmetic>> / +
116-<<d-command-substitution,CommandSubstitution>> / +
48+<<d-command-substitution-quoted,CommandSubstitutionQuoted>> / +
11749 ![+&#96;"&#92;+] .
11850
11951 [[d-parameter]]Parameter::
@@ -121,15 +53,18 @@
12153 +$+ <<d-portable-name,PortableName>> / +
12254 +$+ <<d-parameter-body,ParameterBody>>
12355
56+[[d-portable-name]]PortableName::
57+![++0++-++9++] [++0++-++9++ ++ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_++]+
58+
12459 [[d-parameter-body]]ParameterBody::
12560 +{+ <<d-parameter-number,ParameterNumber>>?
126-(<<d-parameter-name,ParameterName>> / ParameterBody /
127-<<d-parameter,Parameter>>)
61+(<<d-parameter-name,ParameterName>> / ParameterBody / +$+ ParameterBody /
62+<<d-arithmetic,Arithmetic>> / <<d-command-substitution,CommandSubstitution>>)
12863 <<d-parameter-index,ParameterIndex>>? <<d-parameter-match,ParameterMatch>>?
12964 +}+
13065
13166 [[d-parameter-number]]ParameterNumber::
132-`#` ![`+=:/%`] !([`-?#`] `}`)
67+`#` ![`}+=:/%`] !([`-?#`] !`}`)
13368
13469 [[d-parameter-name]]ParameterName::
13570 [+@*#?-$!+] / +
@@ -167,114 +102,193 @@
167102 ![+`()+] .
168103
169104 [[d-command-substitution]]CommandSubstitution::
170-+$(+ <<d-sequence,Sequence>> +)+ / +
105++$(+ <<d-complete-program,CompleteProgram>> +)+ / +
171106 +&#96;+ <<d-command-substitution-body,CommandSubstitutionBody>>* +&#96;+
172107
108+[[d-command-substitution-quoted]]CommandSubstitutionQuoted::
109++$(+ <<d-complete-program,CompleteProgram>> +)+ / +
110++&#96;+ <<d-command-substitution-body-quoted,CommandSubstitutionBodyQuoted>>*
111++&#96;+
112+
173113 [[d-command-substitution-body]]CommandSubstitutionBody::
174114 +\+ [+$&#96;\+] / +
175115 !++&#96;++ .
176116
177-[[d-redirection]]Redirection::
178-<<d-redirection-fd,RedirectionFD>>
179-<<d-redirection-operator,RedirectionOperator>> <<d-s,S>>* <<d-word,Word>> / +
180-RedirectionFD +<(+ <<d-sequence,Sequence>> +)+ / +
181-RedirectionFD +>(+ Sequence +)+
117+[[d-command-substitution-body-quoted]]CommandSubstitutionBodyQuoted::
118++\+ [+$&#96;\`+] / +
119+!++&#96;++ .
182120
183-[[d-redirection-fd]]RedirectionFD::
184-\[[:digit:]]*
121+[[d-special-char]]SpecialChar::
122+[+|&amp;;&lt;&gt;()&#96;&#92;"'+ [:blank:]] / <newline>
185123
186-[[d-redirection-operator]]RedirectionOperator::
187-`<` / `<>` / `>` / `>|` / `>>` / `>>|` / `<&` / `>&` / `<<` / `<<-` / `<<<`
124+The set of terminals of the grammar is the set of characters that can
125+be handled on the environment in which the shell is run (a.k.a. execution
126+character set), with the exception that the set does not contain the null
127+character (`'\0'`).
188128
189-[[d-list-separator]]ListSeparator::
190-<<d-separator,Separator>> / +
191-+&amp;+ <<d-n,N>>* / +
192-&++)++ / +
193-&++;;++
129+Strictly speaking, the definition above is not a complete parsing expression
130+grammar because the rule for <<d-command-substitution,CommandSubstitution>>
131+(<<d-command-substitution-quoted,Quoted>>) depends on
132+<<d-complete-program,CompleteProgram>> which is a non-terminal of the syntax.
194133
195-[[d-separator]]Separator::
196-+;+ <<d-n,N>>* / +
197-<<d-n,N>>+ / +
198-<<d-eof,EOF>>
134+[[classification]]
135+=== Token classification
199136
200-[[d-n]]N::
201-<<d-s,S>>* <<d-nl,NL>>
137+After a word token is delimited, the token may be further classified as an
138+IO_NUMBER token, reserved word, name word, assignment word, or just normal
139+word.
140+Classification other than the normal word is applied only when applicable in
141+the context in which the word appears.
142+See link:syntax.html#tokens[Tokens and keywords] for the list of the reserved
143+words (keywords) and the context in which a word may be recognized as a
144+reserved word.
202145
203-[[d-s]]S::
204-\[[:blank:]] / +
205-<<d-comment,Comment>>
146+A token is an IO_NUMBER token iff it is composed of digit characters only and
147+immediately followed by +<+ or +>+.
206148
149+An assignment token is a token that starts with a name followed by +=+:
150+
151+[[d-assignment-word]]AssignmentWord::
152+<<d-assignment-prefix,AssignmentPrefix>> <<d-word,Word>>
153+
154+[[d-assignment-prefix]]AssignmentPrefix::
155+<<d-name,Name>> +=+
156+
157+[[d-name]]Name::
158+!\[[:digit:]] \[[:alnum:] +_+]+
159+
160+[[comments]]
161+=== Comments
162+
163+A comment begins with `#` and continues up to (but not including) the next
164+newline character.
165+Comments are treated like a blank character and do not become part of a token.
166+The initial `#` of a comment must appear as if it would otherwise be the first
167+character of a word token; Other ++#++s are just treated as part of a word
168+token.
169+
207170 [[d-comment]]Comment::
208-+#+ (!<<d-nl,NL>> .)*
171+`#` (!<newline> .)*
209172
210-[[d-r]]R::
211-<<d-bang,Bang>> / <<d-left-brace,LeftBrace>> / <<d-right-brace,RightBrace>> /
212-<<d-case,Case>> / <<d-do,Do>> / <<d-done,Done>> / <<d-elif,Elif>> /
213-<<d-else,Else>> / <<d-esac,Esac>> / <<d-fi,Fi>> / <<d-for,For>> /
214-<<d-if,If>> / <<d-in,In>> / <<d-then,Then>> / <<d-until,Until>> /
215-<<d-while,While>>
173+[[syntax]]
174+== Syntax
216175
217-[[d-bang]]Bang::
218-+!+ <<d-d,D>>
176+After tokens have been delimited, the sequence of the tokens is parsed
177+according to the context-free grammar defined below, where `*`, `+`, and `?`
178+should be interpreted in the same manner as standard regular expression:
219179
220-[[d-left-brace]]LeftBrace::
221-+{+ <<d-d,D>>
180+[[d-complete-program]]CompleteProgram::
181+<<d-nl,NL>>* | <<d-compound-list,CompoundList>>
222182
223-[[d-right-brace]]RightBrace::
224-+}+ <<d-d,D>>
183+[[d-compound-list]]CompoundList::
184+<<d-nl,NL>>* <<d-and-or-list,AndOrList>>
185+({zwsp}(+;+ | +&+ | NL) <<d-complete-program,CompleteProgram>>)?
225186
226-[[d-case]]Case::
227-+case+ <<d-d,D>>
187+[[d-and-or-list]]AndOrList::
188+<<d-pipeline,Pipeline>> &#40;(+&&+ | +||+) <<d-nl,NL>>* Pipeline)*
228189
229-[[d-do]]Do::
230-+do+ <<d-d,D>>
190+[[d-pipeline]]Pipeline::
191++!+? <<d-command,Command>> (+|+ <<d-nl,NL>>* Command)*
231192
232-[[d-done]]Done::
233-+done+ <<d-d,D>>
193+[[d-command]]Command::
194+<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>* | +
195+<<d-function-definition,FunctionDefinition>> | +
196+<<d-simple-command,SimpleCommand>>
234197
235-[[d-elif]]Elif::
236-+elif+ <<d-d,D>>
198+[[d-compound-command]]CompoundCommand::
199+<<d-subshell,Subshell>> | +
200+<<d-grouping,Grouping>> | +
201+<<d-if-command,IfCommand>> | +
202+<<d-for-command,ForCommand>> | +
203+<<d-while-command,WhileCommand>> | +
204+<<d-case-command,CaseCommand>> | +
205+<<d-function-command,FunctionCommand>>
237206
238-[[d-else]]Else::
239-+else+ <<d-d,D>>
207+[[d-subshell]]Subshell::
208++(+ <<d-compound-list,CompoundList>> +)+
240209
241-[[d-esac]]Esac::
242-+esac+ <<d-d,D>>
210+[[d-grouping]]Grouping::
211++{+ <<d-compound-list,CompoundList>> +}+
243212
244-[[d-fi]]Fi::
245-+fi+ <<d-d,D>>
213+[[d-if-command]]IfCommand::
214++if+ <<d-compound-list,CompoundList>> +then+ CompoundList
215+(+elif+ CompoundList +then+ CompoundList)*
216+(+else+ CompoundList)? +fi+
246217
247-[[d-for]]For::
248-+for+ <<d-d,D>>
218+[[d-for-command]]ForCommand::
219++for+ <<d-name,Name>>
220+({zwsp}(<<d-nl,NL>>* +in+ <<d-word,Word>>*)? (+;+ | NL) NL*)?
221++do+ <<d-compound-list,CompoundList>> +done+
249222
250-[[d-function]]Function::
251-+function+ <<d-d,D>>
223+[[d-while-command]]WhileCommand::
224+(+while+ | +until+) <<d-compound-list,CompoundList>> +do+ CompoundList +done+
252225
253-[[d-if]]If::
254-+if+ <<d-d,D>>
226+[[d-case-command]]CaseCommand::
227++case+ <<d-word,Word>> <<d-nl,NL>>* +in+ NL* <<d-case-list,CaseList>>? +esac+
255228
256-[[d-in]]In::
257-+in+ <<d-d,D>>
229+[[d-case-list]]CaseList::
230+<<d-case-item,CaseItem>> (+;;+ <<d-nl,NL>>* CaseList)?
258231
259-[[d-then]]Then::
260-+then+ <<d-d,D>>
232+[[d-case-item]]CaseItem::
233++(+? <<d-word,Word>> (+|+ Word)* +)+ <<d-complete-program,CompleteProgram>>
261234
262-[[d-until]]Until::
263-+until+ <<d-d,D>>
235+[[d-function-command]]FunctionCommand::
236++function+ <<d-word,Word>> (+(+ +)+)? <<d-nl,NL>>*
237+<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>*
264238
265-[[d-while]]While::
266-+while+ <<d-d,D>>
239+[[d-function-definition]]FunctionDefinition::
240+<<d-name,Name>> +(+ +)+ <<d-nl,NL>>*
241+<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>*
267242
268-[[d-d]]D::
269-!<<d-word,Word>> <<d-s,S>>*
243+[[d-simple-command]]SimpleCommand::
244+(<<d-assignment,Assignment>> | <<d-redirection,Redirection>>) SimpleCommand?
245+| +
246+<<d-word,Word>> (Word | <<d-redirection,Redirection>>)*
270247
271-[[d-special-char]]SpecialChar::
272-[+|&amp;;&lt;&gt;()&#96;&#92;"'+ [:blank:]] / <<d-nl,NL>>
248+[[d-assignment]]Assignment::
249+<<d-assignment-word,AssignmentWord>> | +
250+<<d-assignment-prefix,AssignmentPrefix>>++(++
251+<<d-nl,NL>>* (<<d-word,Word>> NL*)* +)+
273252
253+[[d-redirection]]Redirection::
254+IO_NUMBER? <<d-redirection-operator,RedirectionOperator>> <<d-word,Word>> | +
255+IO_NUMBER? +<(+ <<d-complete-program,CompleteProgram>> +)+ | +
256+IO_NUMBER? +>(+ CompleteProgram +)+
257+
258+[[d-redirection-operator]]RedirectionOperator::
259+`<` | `<>` | `>` | `>|` | `>>` | `>>|` | `<&` | `>&` | `<<` | `<<-` | `<<<`
260+
274261 [[d-nl]]NL::
275262 <newline>
276263
277-[[d-eof]]EOF::
278-!.
264+In the rule for <<d-simple-command,SimpleCommand>>, a <<d-word,Word>> token is
265+accepted only when the token cannot be parsed as the first token of an
266+<<d-assignment,Assignment>>.
279267
268+In the rule for <<d-assignment,Assignment>>, the +(+ token must immediately
269+follow the <<d-assignment-prefix,AssignmentPrefix>> token, without any blank
270+characters in between.
271+
272+link:redir.html#here[Here-document] contents do not appear as part of the
273+grammar above.
274+They are parsed just after the newline (<<d-nl,NL>>) token that follows the
275+corresponding redirection operator.
276+
277+[[alias]]
278+=== Alias substitution
279+
280+Word tokens are subject to link:syntax.html#aliases[alias substitution].
281+
282+- If a word is going to be parsed as a <<d-word,Word>> of a
283+ <<d-simple-command,SimpleCommand>>, the word is subjected to alias
284+ substitution of any kind (normal and global aliases).
285+- If a word is the next token after the result of an alias substitution and
286+ the substitution string ends with a blank character, then the word is also
287+ subjected to alias substitution of any kind.
288+- Other words are subjected to global alias substitution unless the shell is
289+ in the link:posix.html[POSIXly-correct mode].
290+
291+Tokens that are classified as reserved words are not subject to alias
292+substitution.
293+
280294 // vim: set filetype=asciidoc textwidth=78 expandtab:
--- yash/trunk/doc/ja/fgrammar.txt (revision 3888)
+++ yash/trunk/doc/ja/fgrammar.txt (revision 3889)
@@ -4,93 +4,29 @@
44 //:title: Yash マニュアル - 構文の形式的定義
55 :description: Yash のコマンドの構文規則の形式的な定義
66
7-ここに yash の文法の形式的定義を示します。Yash の文法は解析表現文法で定義されます。
7+ここにプログラミング言語としてのシェルの構文定義を示します。
88
9-Yash の文法における終端記号の集合は、yash を実行する環境が扱える任意の文字の集合 (実行文字集合) です (ただしナル文字 `'\0'` を除く)。
9+[NOTE]
10+以下に示す構文の一部はlink:posix.html[POSIX 準拠モード]では使用できません。
1011
11-以下は、yash の文法を構成する非終端記号とそれに対応する終端記号の一覧です。ただしここに挙げる文法の定義にはlink:redir.html#here[ヒアドキュメント]の内容とその終わりを表す行の解析のための規則は含まれていません。また link:posix.html[POSIX 準拠モード]では構文が若干変わりますが、ここには示しません。
12+[[token]]
13+== トークン分割
1214
13-[[d-complete-command]]CompleteCommand::
14-<<d-sequence,Sequence>> <<d-eof,EOF>>
15+入力ソースコードの文字列はまずトークンに分割されます。トークンはソースコード内のより先に現れるトークンができるだけ長くなるように分割します。一つ以上の空白 (blank) 文字の連なりはトークンを分割します。
1516
16-[[d-sequence]]Sequence::
17-<<d-n,N>>* <<d-list,List>>*
17+Yash に存在する演算子トークンは以下の通りです:
1818
19-[[d-list]]List::
20-<<d-pipeline,Pipeline>> ((+&&+ / +||+) <<d-n,N>>* Pipeline)*
21-<<d-list-separator,ListSeparator>>
19+`&` `&&` `(` `)` `;` `;;` `|` `||`
20+`<` `<<` `<&` `<(` `<<-` `<<<` `<>`
21+`>` `>>` `>&` `>(` `>>|` `>|` (改行)
2222
23-[[d-pipeline]]Pipeline::
24-<<d-bang,Bang>>? <<d-command,Command>> (+|+ <<d-n,N>>* Command)*
23+[NOTE]
24+他の一般的なプログラミング言語とは異なり、改行演算子は空白ではなくトークンとして扱われます。
2525
26-[[d-command]]Command::
27-<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>* / +
28-!<<d-r,R>> <<d-function-definition,FunctionDefinition>> / +
29-!R <<d-simple-command,SimpleCommand>>
26+空白ではなく演算子トークンの一部でもない文字は単語 (word) トークンとなります。単語は以下の解析表現文法によって解析されます。
3027
31-[[d-compound-command]]CompoundCommand::
32-<<d-subshell,Subshell>> / +
33-<<d-grouping,Grouping>> / +
34-<<d-if-command,IfCommand>> / +
35-<<d-for-command,ForCommand>> / +
36-<<d-while-command,WhileCommand>> / +
37-<<d-case-command,CaseCommand>> / +
38-<<d-function-command,FunctionCommand>>
39-
40-[[d-subshell]]Subshell::
41-+(+ <<d-sequence,Sequence>> +)+ <<d-s,S>>*
42-
43-[[d-grouping]]Grouping::
44-<<d-left-brace,LeftBrace>> <<d-sequence,Sequence>>
45-<<d-right-brace,RightBrace>>
46-
47-[[d-if-command]]IfCommand::
48-<<d-if,If>> <<d-sequence,Sequence>> <<d-then,Then>> Sequence
49-(<<d-elif,Elif>> Sequence <<d-then,Then>> Sequence)*
50-(<<d-else,Else>> Sequence)? <<d-fi,Fi>>
51-
52-[[d-for-command]]ForCommand::
53-<<d-for,For>> <<d-name,Name>> <<d-s,S>>* <<d-separator,Separator>>?
54-(<<d-in,In>> <<d-word,Word>>* <<d-separator,Separator>>)?
55-<<d-do,Do>> <<d-sequence,Sequence>> <<d-done,Done>>
56-
57-[[d-while-command]]WhileCommand::
58-(<<d-while,While>> / <<d-until,Until>>)
59-<<d-sequence,Sequence>> <<d-do,Do>> Sequence <<d-done,Done>>
60-
61-[[d-case-command]]CaseCommand::
62-<<d-case,Case>> <<d-word,Word>> <<d-n,N>>* <<d-in,In>> N*
63-<<d-case-item,CaseItem>>* <<d-esac,Esac>>
64-
65-[[d-case-item]]CaseItem::
66-!<<d-esac,Esac>> (+(+ <<d-s,S>>*)? <<d-word,Word>> (+|+ S* Word)* +)+
67-<<d-sequence,Sequence>> (+;;+ / &amp;Esac)
68-
69-[[d-function-command]]FunctionCommand::
70-<<d-function,Function>> <<d-word,Word>> (+(+ <<d-s,S>>* +)+)? <<d-n,N>>*
71-<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>*
72-
73-[[d-function-definition]]FunctionDefinition::
74-<<d-name,Name>> <<d-s,S>>* +(+ S* +)+ <<d-n,N>>*
75-<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>*
76-
77-[[d-simple-command]]SimpleCommand::
78-&(<<d-word,Word>> / <<d-redirection,Redirection>>)
79-(<<d-assignment,Assignment>> / Redirection)* (Word / Redirection)*
80-
81-[[d-assignment]]Assignment::
82-<<d-name,Name>> +=+ <<d-word,Word>> / +
83-Name +=(+ <<d-n,N>>* (Word N*)* +)+
84-
85-[[d-name]]Name::
86-!\[[:digit:]] [[:alnum:] +_+]+
87-
88-[[d-portable-name]]PortableName::
89-![++0++-++9++] [++0++-++9++ ++ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_++]+
90-
9128 [[d-word]]Word::
9229 (<<d-word-element,WordElement>> / !<<d-special-char,SpecialChar>> .)+
93-<<d-s,S>>*
9430
9531 [[d-word-element]]WordElement::
9632 +\+ . / +
@@ -101,10 +37,10 @@
10137 <<d-command-substitution,CommandSubstitution>>
10238
10339 [[d-quote-element]]QuoteElement::
104-+\+ ([+$&#96;"&#92;+] / <<d-nl,NL>>) / +
40++\+ ([+$&#96;"&#92;+] / <改行>) / +
10541 <<d-parameter,Parameter>> / +
10642 <<d-arithmetic,Arithmetic>> / +
107-<<d-command-substitution,CommandSubstitution>> / +
43+<<d-command-substitution-quoted,CommandSubstitutionQuoted>> / +
10844 ![+&#96;"&#92;+] .
10945
11046 [[d-parameter]]Parameter::
@@ -112,15 +48,18 @@
11248 +$+ <<d-portable-name,PortableName>> / +
11349 +$+ <<d-parameter-body,ParameterBody>>
11450
51+[[d-portable-name]]PortableName::
52+![++0++-++9++] [++0++-++9++ ++ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_++]+
53+
11554 [[d-parameter-body]]ParameterBody::
11655 +{+ <<d-parameter-number,ParameterNumber>>?
117-(<<d-parameter-name,ParameterName>> / ParameterBody /
118-<<d-parameter,Parameter>>)
56+(<<d-parameter-name,ParameterName>> / ParameterBody / +$+ ParameterBody /
57+<<d-arithmetic,Arithmetic>> / <<d-command-substitution,CommandSubstitution>>)
11958 <<d-parameter-index,ParameterIndex>>? <<d-parameter-match,ParameterMatch>>?
12059 +}+
12160
12261 [[d-parameter-number]]ParameterNumber::
123-`#` ![`+=:/%`] !([`-?#`] `}`)
62+`#` ![`}+=:/%`] !([`-?#`] !`}`)
12463
12564 [[d-parameter-name]]ParameterName::
12665 [+@*#?-$!+] / +
@@ -158,114 +97,159 @@
15897 ![+`()+] .
15998
16099 [[d-command-substitution]]CommandSubstitution::
161-+$(+ <<d-sequence,Sequence>> +)+ / +
100++$(+ <<d-complete-program,CompleteProgram>> +)+ / +
162101 +&#96;+ <<d-command-substitution-body,CommandSubstitutionBody>>* +&#96;+
163102
103+[[d-command-substitution-quoted]]CommandSubstitutionQuoted::
104++$(+ <<d-complete-program,CompleteProgram>> +)+ / +
105++&#96;+ <<d-command-substitution-body-quoted,CommandSubstitutionBodyQuoted>>*
106++&#96;+
107+
164108 [[d-command-substitution-body]]CommandSubstitutionBody::
165109 +\+ [+$&#96;\+] / +
166110 !++&#96;++ .
167111
168-[[d-redirection]]Redirection::
169-<<d-redirection-fd,RedirectionFD>>
170-<<d-redirection-operator,RedirectionOperator>> <<d-s,S>>* <<d-word,Word>> / +
171-RedirectionFD +<(+ <<d-sequence,Sequence>> +)+ / +
172-RedirectionFD +>(+ Sequence +)+
112+[[d-command-substitution-body-quoted]]CommandSubstitutionBodyQuoted::
113++\+ [+$&#96;\`+] / +
114+!++&#96;++ .
173115
174-[[d-redirection-fd]]RedirectionFD::
175-\[[:digit:]]*
116+[[d-special-char]]SpecialChar::
117+[+|&amp;;&lt;&gt;()&#96;&#92;"'+ [:blank:]] / <改行>
176118
177-[[d-redirection-operator]]RedirectionOperator::
178-`<` / `<>` / `>` / `>|` / `>>` / `>>|` / `<&` / `>&` / `<<` / `<<-` / `<<<`
119+この文法における終端記号の集合は、yash を実行する環境が扱える任意の文字の集合 (実行文字集合) です (ただしナル文字 `'\0'` を除く)。
179120
180-[[d-list-separator]]ListSeparator::
181-<<d-separator,Separator>> / +
182-+&amp;+ <<d-n,N>>* / +
183-&++)++ / +
184-&++;;++
121+厳密には、上記の文法定義は完全な解析表現文法ではありません。<<d-command-substitution,CommandSubstitution>> (<<d-command-substitution-quoted,Quoted>>) のルールが構文定義の非終端記号である <<d-complete-program,CompleteProgram>> に依存しているためです。
185122
186-[[d-separator]]Separator::
187-+;+ <<d-n,N>>* / +
188-<<d-n,N>>+ / +
189-<<d-eof,EOF>>
123+[[classification]]
124+=== トークン分類
190125
191-[[d-n]]N::
192-<<d-s,S>>* <<d-nl,NL>>
126+単語トークンが生成された後、単語はさらに IO_NUMBER トークン・予約語・名前・代入・通常の単語のどれかに分類されます。通常の単語以外の分類は、その単語が現れる文脈においてその分類のトークンが現れ得る場合のみ採用されます。予約語の一覧と予約語が認識される文脈の要件は、link:syntax.html#tokens[トークンの解析と予約語]を参照してください。
193127
194-[[d-s]]S::
195-\[[:blank:]] / +
196-<<d-comment,Comment>>
128+トークンが数字のみから構成されていて直後に +<+ または +>+ が続くとき、それは IO_NUMBER トークンとなります。
197129
130+代入 (assignment) トークンは名前 (name) とそれに続く +=+ で始まるトークンです:
131+
132+[[d-assignment-word]]AssignmentWord::
133+<<d-assignment-prefix,AssignmentPrefix>> <<d-word,Word>>
134+
135+[[d-assignment-prefix]]AssignmentPrefix::
136+<<d-name,Name>> +=+
137+
138+[[d-name]]Name::
139+!\[[:digit:]] \[[:alnum:] +_+]+
140+
141+[[comments]]
142+=== コメント
143+
144+コメントは `#` で始まり、次の改行文字の直前まで続きます。コメントは空白と同様に扱われ、トークンの一部にはなりません。コメントを開始する `#` は、トークンの始まりの位置にあるときのみ有効です。それ以外の位置にある `#` は単に単語トークンの一部と見做されます。
145+
198146 [[d-comment]]Comment::
199-+#+ (!<<d-nl,NL>> .)*
147+`#` (!<改行> .)*
200148
201-[[d-r]]R::
202-<<d-bang,Bang>> / <<d-left-brace,LeftBrace>> / <<d-right-brace,RightBrace>> /
203-<<d-case,Case>> / <<d-do,Do>> / <<d-done,Done>> / <<d-elif,Elif>> /
204-<<d-else,Else>> / <<d-esac,Esac>> / <<d-fi,Fi>> / <<d-for,For>> /
205-<<d-if,If>> / <<d-in,In>> / <<d-then,Then>> / <<d-until,Until>> /
206-<<d-while,While>>
149+[[syntax]]
150+== 構文
207151
208-[[d-bang]]Bang::
209-+!+ <<d-d,D>>
152+トークンが分割された後、その結果であるトークンの並びは以下に示す文脈自由文法によって解析されます。(以下、`*` と `+` と `?` は正規表現と同様の意味を持ちます)
210153
211-[[d-left-brace]]LeftBrace::
212-+{+ <<d-d,D>>
154+[[d-complete-program]]CompleteProgram::
155+<<d-nl,NL>>* | <<d-compound-list,CompoundList>>
213156
214-[[d-right-brace]]RightBrace::
215-+}+ <<d-d,D>>
157+[[d-compound-list]]CompoundList::
158+<<d-nl,NL>>* <<d-and-or-list,AndOrList>>
159+({zwsp}(+;+ | +&+ | NL) <<d-complete-program,CompleteProgram>>)?
216160
217-[[d-case]]Case::
218-+case+ <<d-d,D>>
161+[[d-and-or-list]]AndOrList::
162+<<d-pipeline,Pipeline>> &#40;(+&&+ | +||+) <<d-nl,NL>>* Pipeline)*
219163
220-[[d-do]]Do::
221-+do+ <<d-d,D>>
164+[[d-pipeline]]Pipeline::
165++!+? <<d-command,Command>> (+|+ <<d-nl,NL>>* Command)*
222166
223-[[d-done]]Done::
224-+done+ <<d-d,D>>
167+[[d-command]]Command::
168+<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>* | +
169+<<d-function-definition,FunctionDefinition>> | +
170+<<d-simple-command,SimpleCommand>>
225171
226-[[d-elif]]Elif::
227-+elif+ <<d-d,D>>
172+[[d-compound-command]]CompoundCommand::
173+<<d-subshell,Subshell>> | +
174+<<d-grouping,Grouping>> | +
175+<<d-if-command,IfCommand>> | +
176+<<d-for-command,ForCommand>> | +
177+<<d-while-command,WhileCommand>> | +
178+<<d-case-command,CaseCommand>> | +
179+<<d-function-command,FunctionCommand>>
228180
229-[[d-else]]Else::
230-+else+ <<d-d,D>>
181+[[d-subshell]]Subshell::
182++(+ <<d-compound-list,CompoundList>> +)+
231183
232-[[d-esac]]Esac::
233-+esac+ <<d-d,D>>
184+[[d-grouping]]Grouping::
185++{+ <<d-compound-list,CompoundList>> +}+
234186
235-[[d-fi]]Fi::
236-+fi+ <<d-d,D>>
187+[[d-if-command]]IfCommand::
188++if+ <<d-compound-list,CompoundList>> +then+ CompoundList
189+(+elif+ CompoundList +then+ CompoundList)*
190+(+else+ CompoundList)? +fi+
237191
238-[[d-for]]For::
239-+for+ <<d-d,D>>
192+[[d-for-command]]ForCommand::
193++for+ <<d-name,Name>>
194+({zwsp}(<<d-nl,NL>>* +in+ <<d-word,Word>>*)? (+;+ | NL) NL*)?
195++do+ <<d-compound-list,CompoundList>> +done+
240196
241-[[d-function]]Function::
242-+function+ <<d-d,D>>
197+[[d-while-command]]WhileCommand::
198+(+while+ | +until+) <<d-compound-list,CompoundList>> +do+ CompoundList +done+
243199
244-[[d-if]]If::
245-+if+ <<d-d,D>>
200+[[d-case-command]]CaseCommand::
201++case+ <<d-word,Word>> <<d-nl,NL>>* +in+ NL* <<d-case-list,CaseList>>? +esac+
246202
247-[[d-in]]In::
248-+in+ <<d-d,D>>
203+[[d-case-list]]CaseList::
204+<<d-case-item,CaseItem>> (+;;+ <<d-nl,NL>>* CaseList)?
249205
250-[[d-then]]Then::
251-+then+ <<d-d,D>>
206+[[d-case-item]]CaseItem::
207++(+? <<d-word,Word>> (+|+ Word)* +)+ <<d-complete-program,CompleteProgram>>
252208
253-[[d-until]]Until::
254-+until+ <<d-d,D>>
209+[[d-function-command]]FunctionCommand::
210++function+ <<d-word,Word>> (+(+ +)+)? <<d-nl,NL>>*
211+<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>*
255212
256-[[d-while]]While::
257-+while+ <<d-d,D>>
213+[[d-function-definition]]FunctionDefinition::
214+<<d-name,Name>> +(+ +)+ <<d-nl,NL>>*
215+<<d-compound-command,CompoundCommand>> <<d-redirection,Redirection>>*
258216
259-[[d-d]]D::
260-!<<d-word,Word>> <<d-s,S>>*
217+[[d-simple-command]]SimpleCommand::
218+(<<d-assignment,Assignment>> | <<d-redirection,Redirection>>) SimpleCommand?
219+| +
220+<<d-word,Word>> (Word | <<d-redirection,Redirection>>)*
261221
262-[[d-special-char]]SpecialChar::
263-[+|&amp;;&lt;&gt;()&#96;&#92;"'+ [:blank:]] / <<d-nl,NL>>
222+[[d-assignment]]Assignment::
223+<<d-assignment-word,AssignmentWord>> | +
224+<<d-assignment-prefix,AssignmentPrefix>>++(++
225+<<d-nl,NL>>* (<<d-word,Word>> NL*)* +)+
264226
227+[[d-redirection]]Redirection::
228+IO_NUMBER? <<d-redirection-operator,RedirectionOperator>> <<d-word,Word>> | +
229+IO_NUMBER? +<(+ <<d-complete-program,CompleteProgram>> +)+ | +
230+IO_NUMBER? +>(+ CompleteProgram +)+
231+
232+[[d-redirection-operator]]RedirectionOperator::
233+`<` | `<>` | `>` | `>|` | `>>` | `>>|` | `<&` | `>&` | `<<` | `<<-` | `<<<`
234+
265235 [[d-nl]]NL::
266-<newline>
236+<改行>
267237
268-[[d-eof]]EOF::
269-!.
238+ルール <<d-simple-command,SimpleCommand>> では、<<d-word,Word>> トークンはそれが <<d-assignment,Assignment>> の始まりとは解釈できない場合にのみ採用されます。
270239
240+ルール <<d-assignment,Assignment>> では、<<d-assignment-prefix,AssignmentPrefix>> と ++(++ の間に空白を置くことはできません。
241+
242+上記の文法定義にはlink:redir.html#here[ヒアドキュメント]の内容とその終わりを表す行の解析のための規則は含まれていません。それらは対応するリダイレクト演算子の後にある最初の改行 (<<d-nl,NL>>) トークンが解析された直後に解析されます。
243+
244+[[alias]]
245+=== エイリアス置換
246+
247+単語はlink:syntax.html#aliases[エイリアス置換]の対象となります。
248+
249+- 単語が <<d-simple-command,SimpleCommand>> の <<d-word,Word>> として解析されようとした時に、通常のエイリアス及びグローバルエイリアスを対象として置換が試みられます。
250+- 置換結果が空白文字 (blank) で終わるエイリアス置換の次に単語トークンがある場合、その単語も通常のエイリアス及びグローバルエイリアスを対象として置換が試みられます。
251+- その他の単語は、グローバルエイリアスのみを対象として置換が試みられます。(link:posix.html[POSIX 準拠モード]を除く)
252+
253+予約語に分類されたトークンはエイリアス置換の対象からは除外されます。
254+
271255 // vim: set filetype=asciidoc expandtab:
--- yash/trunk/parser.c (revision 3888)
+++ yash/trunk/parser.c (revision 3889)
@@ -49,6 +49,8 @@
4949 static void pipesfree(pipeline_T *p);
5050 static void ifcmdsfree(ifcommand_T *i);
5151 static void caseitemsfree(caseitem_T *i);
52+static void wordunitfree(wordunit_T *wu)
53+ __attribute__((nonnull));
5254 static void wordfree_vp(void *w);
5355 static void assignsfree(assign_T *a);
5456 static void redirsfree(redir_T *r);
@@ -144,26 +146,30 @@
144146 }
145147 }
146148
149+void wordunitfree(wordunit_T *wu)
150+{
151+ switch (wu->wu_type) {
152+ case WT_STRING:
153+ free(wu->wu_string);
154+ break;
155+ case WT_PARAM:
156+ paramfree(wu->wu_param);
157+ break;
158+ case WT_CMDSUB:
159+ embedcmdfree(wu->wu_cmdsub);
160+ break;
161+ case WT_ARITH:
162+ wordfree(wu->wu_arith);
163+ break;
164+ }
165+ free(wu);
166+}
167+
147168 void wordfree(wordunit_T *w)
148169 {
149170 while (w != NULL) {
150- switch (w->wu_type) {
151- case WT_STRING:
152- free(w->wu_string);
153- break;
154- case WT_PARAM:
155- paramfree(w->wu_param);
156- break;
157- case WT_CMDSUB:
158- embedcmdfree(w->wu_cmdsub);
159- break;
160- case WT_ARITH:
161- wordfree(w->wu_arith);
162- break;
163- }
164-
165171 wordunit_T *next = w->next;
166- free(w);
172+ wordunitfree(w);
167173 w = next;
168174 }
169175 }
@@ -242,6 +248,24 @@
242248
243249 /********** Auxiliary Functions for Parser **********/
244250
251+typedef enum tokentype_T {
252+ TT_UNKNOWN,
253+ TT_END_OF_INPUT,
254+ TT_WORD,
255+ TT_IO_NUMBER,
256+ /* operators */
257+ TT_NEWLINE,
258+ TT_AMP, TT_AMPAMP, TT_LPAREN, TT_RPAREN, TT_SEMICOLON, TT_DOUBLE_SEMICOLON,
259+ TT_PIPE, TT_PIPEPIPE, TT_LESS, TT_LESSLESS, TT_LESSAMP, TT_LESSLESSDASH,
260+ TT_LESSLESSLESS, TT_LESSGREATER, TT_LESSLPAREN, TT_GREATER,
261+ TT_GREATERGREATER, TT_GREATERGREATERPIPE, TT_GREATERPIPE, TT_GREATERAMP,
262+ TT_GREATERLPAREN,
263+ /* reserved words */
264+ TT_IF, TT_THEN, TT_ELSE, TT_ELIF, TT_FI, TT_DO, TT_DONE, TT_CASE, TT_ESAC,
265+ TT_WHILE, TT_UNTIL, TT_FOR, TT_LBRACE, TT_RBRACE, TT_BANG, TT_IN,
266+ TT_FUNCTION,
267+} tokentype_T;
268+
245269 static wchar_t *skip_name(const wchar_t *s, bool predicate(wchar_t))
246270 __attribute__((pure,nonnull));
247271 static bool is_name_by_predicate(const wchar_t *s, bool predicate(wchar_t))
@@ -248,8 +272,18 @@
248272 __attribute__((pure,nonnull));
249273 static bool is_portable_name(const wchar_t *s)
250274 __attribute__((pure,nonnull));
251-static bool is_literal_function_name(const wordunit_T *wu)
275+static tokentype_T identify_reserved_word_string(const wchar_t *s)
276+ __attribute__((pure,nonnull));
277+static bool is_single_string_word(const wordunit_T *wu)
252278 __attribute__((pure));
279+static bool is_digits_only(const wordunit_T *wu)
280+ __attribute__((pure));
281+static bool is_name_word(const wordunit_T *wu)
282+ __attribute__((pure));
283+static tokentype_T identify_reserved_word(const wordunit_T *wu)
284+ __attribute__((pure));
285+static bool is_closing_tokentype(tokentype_T tt)
286+ __attribute__((const));
253287
254288
255289 /* Checks if the specified character can be used in a portable variable name.
@@ -313,8 +347,9 @@
313347 return is_name_by_predicate(s, is_name_char);
314348 }
315349
316-/* Returns true iff the string is a reserved word. */
317-bool is_keyword(const wchar_t *s)
350+/* Converts a string to the corresponding token type. Returns TT_WORD for
351+ * non-reserved words. */
352+tokentype_T identify_reserved_word_string(const wchar_t *s)
318353 {
319354 /* List of keywords:
320355 * case do done elif else esac fi for function if in then until while
@@ -323,98 +358,213 @@
323358 * select [[ ]] */
324359 switch (s[0]) {
325360 case L'c':
326- return s[1] == L'a' && s[2] == L's' && s[3] == L'e' && s[4]== L'\0';
361+ if (s[1] == L'a' && s[2] == L's' && s[3] == L'e' && s[4]== L'\0')
362+ return TT_CASE;
363+ break;
327364 case L'd':
328- return s[1] == L'o' && (s[2] == L'\0' ||
329- (s[2] == L'n' && s[3] == L'e' && s[4] == L'\0'));
365+ if (s[1] == L'o') {
366+ if (s[2] == L'\0')
367+ return TT_DO;
368+ if (s[2] == L'n' && s[3] == L'e' && s[4] == L'\0')
369+ return TT_DONE;
370+ }
371+ break;
330372 case L'e':
331- return ((s[1] == L'l'
332- && ((s[2] == L's' && s[3] == L'e')
333- || (s[2] == L'i' && s[3] == L'f')))
334- || (s[1] == L's' && s[2] == L'a' && s[3] == L'c'))
335- && s[4] == L'\0';
373+ if (s[1] == L'l') {
374+ if (s[2] == L's' && s[3] == L'e' && s[4] == L'\0')
375+ return TT_ELSE;
376+ if (s[2] == L'i' && s[3] == L'f' && s[4] == L'\0')
377+ return TT_ELIF;
378+ }
379+ if (s[1] == L's' && s[2] == L'a' && s[3] == L'c' && s[4] == L'\0')
380+ return TT_ESAC;
381+ break;
336382 case L'f':
337- return (s[1] == L'i' && s[2] == L'\0')
338- || (s[1] == L'o' && s[2] == L'r' && s[3] == L'\0')
339- || (s[1] == L'u' && s[2] == L'n' && s[3] == L'c'
340- && s[4] == L't' && s[5] == L'i' && s[6] == L'o'
341- && s[7] == L'n' && s[8] == L'\0');
383+ if (s[1] == L'i' && s[2] == L'\0')
384+ return TT_FI;
385+ if (s[1] == L'o' && s[2] == L'r' && s[3] == L'\0')
386+ return TT_FOR;
387+ if (s[1] == L'u' && s[2] == L'n' && s[3] == L'c' && s[4] == L't' &&
388+ s[5] == L'i' && s[6] == L'o' && s[7] == L'n' &&
389+ s[8] == L'\0')
390+ return TT_FUNCTION;
391+ break;
342392 case L'i':
343- return (s[1] == L'f' || s[1] == L'n') && s[2] == L'\0';
393+ if (s[1] == L'f' && s[2] == L'\0')
394+ return TT_IF;
395+ if (s[1] == L'n' && s[2] == L'\0')
396+ return TT_IN;
397+ break;
344398 case L't':
345- return s[1] == L'h' && s[2] == L'e' && s[3] == L'n' && s[4]== L'\0';
399+ if (s[1] == L'h' && s[2] == L'e' && s[3] == L'n' && s[4]== L'\0')
400+ return TT_THEN;
401+ break;
346402 case L'u':
347- return s[1] == L'n' && s[2] == L't' && s[3] == L'i' && s[4] == L'l'
348- && s[5] == L'\0';
403+ if (s[1] == L'n' && s[2] == L't' && s[3] == L'i' && s[4] == L'l' &&
404+ s[5] == L'\0')
405+ return TT_UNTIL;
406+ break;
349407 case L'w':
350- return s[1] == L'h' && s[2] == L'i' && s[3] == L'l' && s[4] == L'e'
351- && s[5] == L'\0';
408+ if (s[1] == L'h' && s[2] == L'i' && s[3] == L'l' && s[4] == L'e' &&
409+ s[5] == L'\0')
410+ return TT_WHILE;
411+ break;
352412 case L'{':
413+ if (s[1] == L'\0')
414+ return TT_LBRACE;
415+ break;
353416 case L'}':
417+ if (s[1] == L'\0')
418+ return TT_RBRACE;
419+ break;
354420 case L'!':
355- return s[1] == L'\0';
356- default:
357- return false;
421+ if (s[1] == L'\0')
422+ return TT_BANG;
423+ break;
358424 }
425+ return TT_WORD;
359426 }
360427
361-bool is_literal_function_name(const wordunit_T *wu)
428+/* Returns true iff the string is a reserved word. */
429+bool is_keyword(const wchar_t *s)
362430 {
363- if (wu == NULL)
431+ return identify_reserved_word_string(s) != TT_WORD;
432+}
433+
434+bool is_single_string_word(const wordunit_T *wu)
435+{
436+ return wu != NULL && wu->next == NULL && wu->wu_type == WT_STRING;
437+}
438+
439+/* Tests if a word is made up of digits only. */
440+bool is_digits_only(const wordunit_T *wu)
441+{
442+ if (!is_single_string_word(wu))
364443 return false;
365- if (wu->next != NULL)
444+
445+ const wchar_t *s = wu->wu_string;
446+ assert(s[0] != L'\0');
447+ while (iswdigit(*s))
448+ s++;
449+ return *s == L'\0';
450+}
451+
452+bool is_name_word(const wordunit_T *wu)
453+{
454+ if (!is_single_string_word(wu))
366455 return false;
367- if (wu->wu_type != WT_STRING)
368- return false;
369456
370457 return (posixly_correct ? is_portable_name : is_name)(wu->wu_string);
371458 }
372459
460+/* Converts a word to the corresponding token type. Returns TT_WORD for
461+ * non-reserved words. */
462+tokentype_T identify_reserved_word(const wordunit_T *wu)
463+{
464+ if (!is_single_string_word(wu))
465+ return TT_WORD;
466+ return identify_reserved_word_string(wu->wu_string);
467+}
373468
469+/* Determines if the specified token is a 'closing' token such as ")", "}", and
470+ * "fi". Closing tokens delimit and/or lists. */
471+bool is_closing_tokentype(tokentype_T tt)
472+{
473+ switch (tt) {
474+ case TT_RPAREN:
475+ case TT_RBRACE:
476+ case TT_THEN:
477+ case TT_ELIF:
478+ case TT_ELSE:
479+ case TT_FI:
480+ case TT_DO:
481+ case TT_DONE:
482+ case TT_DOUBLE_SEMICOLON:
483+ case TT_ESAC:
484+ return true;
485+ default:
486+ return false;
487+ }
488+}
489+
490+
374491 /********** Parser **********/
375492
376493 /* Holds data that are used in parsing. */
377494 typedef struct parsestate_T {
495+ /* contains parameters that affect the behavior of parsing */
378496 parseparam_T *info;
497+ /* true iff any parsing error occurred */
379498 bool error;
499+ /* the source code being parsed */
380500 struct xwcsbuf_T src;
501+ /* the position of the current character or `token' */
381502 size_t index;
503+ /* the index just past the current `token' */
504+ size_t next_index;
505+ /* type of the current `token' */
506+ tokentype_T tokentype;
507+ /* the current token (NULL when `tokentype' is an operator token) */
508+ wordunit_T *token;
509+ /* here-documents whose contents have not been read */
382510 struct plist_T pending_heredocs;
383- bool enable_alias, reparse;
511+ /* false when alias substitution is suppressed */
512+ bool enable_alias;
513+ /* true when alias substitution has occurred */
514+ bool reparse;
515+ /* record of alias substitutions that are responsible for the current
516+ * `startindex' */
384517 struct aliaslist_T *aliases;
385518 } parsestate_T;
386-/* info: contains parameter that affect the behavior of parsing.
387- * error: set to true when a parsing error occurs.
388- * src: a buffer that contains the source code to parse.
389- * index: the index to the string in `src'.
390- * indicates the character position that is being parsed.
391- * pending_heredocs: a list of here-documents whose contents have not been read.
392- * enable_alias: indicates if alias substitution should be performed.
393- * reparse: indicates that the current word must be re-parsed because
394- * alias substitution has been performed at the current position.
395- * aliases: a list of alias substitutions that were performed at the current
396- * position. */
397519
398520 static void serror(parsestate_T *restrict ps, const char *restrict format, ...)
399521 __attribute__((nonnull(1,2),format(printf,2,3)));
522+static const char *get_errmsg_unexpected_tokentype(tokentype_T tokentype)
523+ __attribute__((const));
524+static void print_errmsg_token_unexpected(parsestate_T *ps)
525+ __attribute__((nonnull));
526+static void print_errmsg_token_missing(parsestate_T *ps, const wchar_t *t)
527+ __attribute__((nonnull));
528+
400529 static inputresult_T read_more_input(parsestate_T *ps)
401530 __attribute__((nonnull));
402531 static void line_continuation(parsestate_T *ps, size_t index)
403532 __attribute__((nonnull));
533+static void maybe_line_continuations(parsestate_T *ps, size_t index)
534+ __attribute__((nonnull));
404535 static void rewind_index(parsestate_T *ps, size_t to)
405536 __attribute__((nonnull));
406-static void ensure_buffer(parsestate_T *ps, size_t n)
407- __attribute__((nonnull));
408537 static size_t count_name_length(parsestate_T *ps, bool isnamechar(wchar_t c))
409538 __attribute__((nonnull));
410-static void skip_blanks_and_comment(parsestate_T *ps)
539+
540+static void next_token(parsestate_T *ps)
411541 __attribute__((nonnull));
412-static bool skip_to_next_token(parsestate_T *ps)
542+static wordunit_T *parse_word(parsestate_T *ps, bool testfunc(wchar_t c))
543+ __attribute__((nonnull,malloc,warn_unused_result));
544+static void skip_to_next_single_quote(parsestate_T *ps)
413545 __attribute__((nonnull));
546+static wordunit_T *parse_special_word_unit(parsestate_T *ps, bool indq)
547+ __attribute__((nonnull,malloc,warn_unused_result));
548+static wordunit_T *tryparse_paramexp_raw(parsestate_T *ps)
549+ __attribute__((nonnull,malloc,warn_unused_result));
550+static wordunit_T *parse_paramexp_in_brace(parsestate_T *ps)
551+ __attribute__((nonnull,malloc,warn_unused_result));
552+static wordunit_T *parse_cmdsubst_in_paren(parsestate_T *ps)
553+ __attribute__((nonnull,malloc,warn_unused_result));
554+static embedcmd_T extract_command_in_paren(parsestate_T *ps)
555+ __attribute__((nonnull,warn_unused_result));
556+static wchar_t *extract_command_in_paren_unparsed(parsestate_T *ps)
557+ __attribute__((nonnull,malloc,warn_unused_result));
558+static wordunit_T *parse_cmdsubst_in_backquote(parsestate_T *ps, bool bsbq)
559+ __attribute__((nonnull,malloc,warn_unused_result));
560+static wordunit_T *tryparse_arith(parsestate_T *ps)
561+ __attribute__((nonnull,malloc,warn_unused_result));
562+
414563 static void next_line(parsestate_T *ps)
415564 __attribute__((nonnull));
416-static bool is_command_delimiter_char(wchar_t c)
417- __attribute__((const));
565+static bool parse_newline_list(parsestate_T *ps)
566+ __attribute__((nonnull));
567+
418568 static bool is_comma_or_closing_bracket(wchar_t c)
419569 __attribute__((const));
420570 static bool is_slash_or_closing_brace(wchar_t c)
@@ -421,16 +571,12 @@
421571 __attribute__((const));
422572 static bool is_closing_brace(wchar_t c)
423573 __attribute__((const));
424-static bool has_token(const parsestate_T *ps, const wchar_t *t)
425- __attribute__((pure,nonnull));
426-static const wchar_t *check_opening_token(parsestate_T *ps)
427- __attribute__((nonnull));
428-static const wchar_t *check_closing_token(parsestate_T *ps)
429- __attribute__((nonnull));
574+
430575 static bool psubstitute_alias(parsestate_T *ps, substaliasflags_T f)
431576 __attribute__((nonnull));
432577 static void psubstitute_alias_recursive(parsestate_T *ps, substaliasflags_T f)
433578 __attribute__((nonnull));
579+
434580 static and_or_T *parse_command_list(parsestate_T *ps, bool toeol)
435581 __attribute__((nonnull,malloc,warn_unused_result));
436582 static and_or_T *parse_compound_list(parsestate_T *ps)
@@ -445,53 +591,26 @@
445591 __attribute__((nonnull,malloc,warn_unused_result));
446592 static command_T *parse_command(parsestate_T *ps)
447593 __attribute__((nonnull,malloc,warn_unused_result));
448-static redir_T **parse_assignments_and_redirects(parsestate_T *ps, command_T *c)
594+static void **parse_simple_command_tokens(
595+ parsestate_T *ps, assign_T **assigns, redir_T **redirs)
449596 __attribute__((nonnull,malloc,warn_unused_result));
450-static void **parse_words_and_redirects(
451- parsestate_T *ps, redir_T **redirlastp, bool first)
597+static void **parse_words(parsestate_T *ps, bool skip_newlines)
452598 __attribute__((nonnull,malloc,warn_unused_result));
453599 static void parse_redirect_list(parsestate_T *ps, redir_T **lastp)
454600 __attribute__((nonnull));
455601 static assign_T *tryparse_assignment(parsestate_T *ps)
456602 __attribute__((nonnull,malloc,warn_unused_result));
457-static void **parse_words_to_paren(parsestate_T *ps)
458- __attribute__((nonnull,malloc,warn_unused_result));
459603 static redir_T *tryparse_redirect(parsestate_T *ps)
460604 __attribute__((nonnull,malloc,warn_unused_result));
461-static wordunit_T *parse_word(parsestate_T *ps, bool globalaliases)
605+static command_T *parse_compound_command(parsestate_T *ps)
462606 __attribute__((nonnull,malloc,warn_unused_result));
463-static wordunit_T *parse_word_to(parsestate_T *ps, bool testfunc(wchar_t c))
607+static command_T *parse_group(parsestate_T *ps)
464608 __attribute__((nonnull,malloc,warn_unused_result));
465-static void skip_to_next_single_quote(parsestate_T *ps)
466- __attribute__((nonnull));
467-static wordunit_T *parse_special_word_unit(parsestate_T *ps, bool indq)
468- __attribute__((nonnull,malloc,warn_unused_result));
469-static wordunit_T *tryparse_paramexp_raw(parsestate_T *ps)
470- __attribute__((nonnull,malloc,warn_unused_result));
471-static wordunit_T *parse_paramexp_in_brace(parsestate_T *ps)
472- __attribute__((nonnull,malloc,warn_unused_result));
473-static wordunit_T *parse_cmdsubst_in_paren(parsestate_T *ps)
474- __attribute__((nonnull,malloc,warn_unused_result));
475-static embedcmd_T extract_command_in_paren(parsestate_T *ps)
476- __attribute__((nonnull,warn_unused_result));
477-static wchar_t *extract_command_in_paren_unparsed(parsestate_T *ps)
478- __attribute__((nonnull,malloc,warn_unused_result));
479-static wordunit_T *parse_cmdsubst_in_backquote(parsestate_T *ps, bool bsbq)
480- __attribute__((nonnull,malloc,warn_unused_result));
481-static wordunit_T *tryparse_arith(parsestate_T *ps)
482- __attribute__((nonnull,malloc,warn_unused_result));
483-static wchar_t *parse_word_as_wcs(parsestate_T *ps)
484- __attribute__((nonnull,malloc,warn_unused_result));
485-static command_T *parse_compound_command(
486- parsestate_T *ps, const wchar_t *command)
487- __attribute__((nonnull,malloc,warn_unused_result));
488-static command_T *parse_group(parsestate_T *ps, commandtype_T type)
489- __attribute__((nonnull,malloc,warn_unused_result));
490609 static command_T *parse_if(parsestate_T *ps)
491610 __attribute__((nonnull,malloc,warn_unused_result));
492611 static command_T *parse_for(parsestate_T *ps)
493612 __attribute__((nonnull,malloc,warn_unused_result));
494-static command_T *parse_while(parsestate_T *ps, bool whltype)
613+static command_T *parse_while(parsestate_T *ps)
495614 __attribute__((nonnull,malloc,warn_unused_result));
496615 static command_T *parse_case(parsestate_T *ps)
497616 __attribute__((nonnull,malloc,warn_unused_result));
@@ -503,6 +622,7 @@
503622 __attribute__((nonnull,malloc,warn_unused_result));
504623 static command_T *try_reparse_as_function(parsestate_T *ps, command_T *c)
505624 __attribute__((nonnull,warn_unused_result));
625+
506626 static void read_heredoc_contents(parsestate_T *ps, redir_T *redir)
507627 __attribute__((nonnull));
508628 static void read_heredoc_contents_without_expansion(
@@ -519,20 +639,20 @@
519639 parsestate_T *ps, bool backquote, bool stoponnewline,
520640 wordunit_T **lastp)
521641 __attribute__((nonnull));
522-static const char *get_errmsg_unexpected_token(const wchar_t *t)
523- __attribute__((nonnull));
524-static void print_errmsg_token_missing(parsestate_T *ps, const wchar_t *t)
525- __attribute__((nonnull));
526642
527643 #define QUOTES L"\"'\\"
528644
529645
646+/***** Entry points *****/
647+
530648 /* The functions below may return non-NULL even on error.
531649 * The error condition must be tested by the `error' flag of the parsestate_T
532650 * structure. It is set to true when `serror' is called. */
533651 /* Every function named `parse_*' advances the current position (the `index'
534652 * value of the parsestate_T structure) to the index of the first character
535- * that has not yet been parsed. */
653+ * that has not yet been parsed. Syntax parser functions also update the
654+ * current `token' and `tokentype' to the first unconsumed token, in which
655+ * case `index' points to the first character of the `token'. */
536656
537657
538658 /* The main entry point to the parser.
@@ -555,6 +675,9 @@
555675 .info = info,
556676 .error = false,
557677 .index = 0,
678+ .next_index = 0,
679+ .tokentype = TT_UNKNOWN,
680+ .token = NULL,
558681 .enable_alias = info->enable_alias,
559682 .reparse = false,
560683 .aliases = NULL,
@@ -576,6 +699,7 @@
576699 wb_destroy(&ps.src);
577700 pl_destroy(&ps.pending_heredocs);
578701 destroy_aliaslist(ps.aliases);
702+ wordfree(ps.token);
579703
580704 switch (ps.info->lastinputresult) {
581705 case INPUT_OK:
@@ -602,6 +726,52 @@
602726 assert(false);
603727 }
604728
729+/* Parses a string recognizing parameter expansions, command substitutions of
730+ * the form "$(...)" and arithmetic expansions.
731+ * All the members of `info' except `lastinputresult' must have been initialized
732+ * beforehand.
733+ * This function reads and parses the input to the end of file.
734+ * Iff successful, the result is assigned to `*resultp' and true is returned.
735+ * If the input is empty, NULL is assigned.
736+ * On error, the value of `*resultp' is undefined. */
737+bool parse_string(parseparam_T *info, wordunit_T **restrict resultp)
738+{
739+ parsestate_T ps = {
740+ .info = info,
741+ .error = false,
742+ .index = 0,
743+ .next_index = 0,
744+ .tokentype = TT_UNKNOWN,
745+ .token = NULL,
746+ .enable_alias = false,
747+ .reparse = false,
748+ .aliases = NULL,
749+ };
750+ wb_init(&ps.src);
751+
752+ ps.info->lastinputresult = INPUT_OK;
753+ read_more_input(&ps);
754+ pl_init(&ps.pending_heredocs);
755+
756+ resultp = parse_string_without_quotes(&ps, false, false, resultp);
757+ *resultp = NULL;
758+
759+ wb_destroy(&ps.src);
760+ pl_destroy(&ps.pending_heredocs);
761+ assert(ps.aliases == NULL);
762+ //destroy_aliaslist(ps.aliases);
763+ wordfree(ps.token);
764+
765+ if (ps.info->lastinputresult != INPUT_EOF || ps.error) {
766+ wordfree(*resultp);
767+ return false;
768+ } else {
769+ return true;
770+ }
771+}
772+
773+/***** Error message utility *****/
774+
605775 /* Prints the specified error message to the standard error.
606776 * `format' is passed to `gettext' in this function.
607777 * `format' need not to have a trailing newline since a newline is automatically
@@ -625,6 +795,64 @@
625795 ps->error = true;
626796 }
627797
798+const char *get_errmsg_unexpected_tokentype(tokentype_T tokentype)
799+{
800+ switch (tokentype) {
801+ case TT_RPAREN:
802+ return Ngt("encountered `%ls' without a matching `('");
803+ case TT_RBRACE:
804+ return Ngt("encountered `%ls' without a matching `{'");
805+ case TT_DOUBLE_SEMICOLON:
806+ return Ngt("`%ls' is used outside `case'");
807+ case TT_BANG:
808+ return Ngt("`%ls' cannot be used as a command name");
809+ case TT_IN:
810+ return Ngt("`%ls' cannot be used as a command name");
811+ case TT_FI:
812+ return Ngt("encountered `%ls' "
813+ "without a matching `if' and/or `then'");
814+ case TT_THEN:
815+ return Ngt("encountered `%ls' without a matching `if' or `elif'");
816+ case TT_DO:
817+ return Ngt("encountered `%ls' "
818+ "without a matching `for', `while', or `until'");
819+ case TT_DONE:
820+ return Ngt("encountered `%ls' without a matching `do'");
821+ case TT_ESAC:
822+ return Ngt("encountered `%ls' without a matching `case'");
823+ case TT_ELIF:
824+ case TT_ELSE:
825+ return Ngt("encountered `%ls' "
826+ "without a matching `if' and/or `then'");
827+ default:
828+ assert(false);
829+ }
830+}
831+
832+void print_errmsg_token_unexpected(parsestate_T *ps)
833+{
834+ assert(ps->index <= ps->next_index);
835+ size_t length = ps->next_index - ps->index;
836+ wchar_t token[length + 1];
837+ wcsncpy(token, &ps->src.contents[ps->index], length);
838+ token[length] = L'\0';
839+
840+ const char *message = get_errmsg_unexpected_tokentype(ps->tokentype);
841+ serror(ps, message, token);
842+}
843+
844+void print_errmsg_token_missing(parsestate_T *ps, const wchar_t *t)
845+{
846+ if (is_closing_tokentype(ps->tokentype)) {
847+ print_errmsg_token_unexpected(ps);
848+ serror(ps, Ngt("(maybe you missed `%ls'?)"), t);
849+ } else {
850+ serror(ps, Ngt("`%ls' is missing"), t);
851+ }
852+}
853+
854+/***** Input buffer manipulators *****/
855+
628856 /* Reads the next line of input and returns the result type, which is assigned
629857 * to `ps->info->lastinputresult'.
630858 * If `ps->info->lastinputresult' is not INPUT_OK, it is simply returned
@@ -662,7 +890,21 @@
662890 read_more_input(ps);
663891 }
664892
893+/* Removes line continuations at the specified index.
894+ * The next line will be read if the removed line continuation is at the end of
895+ * the buffer. */
896+void maybe_line_continuations(parsestate_T *ps, size_t index)
897+{
898+ assert(index <= ps->src.length);
899+ if (index == ps->src.length)
900+ read_more_input(ps);
901+ while (ps->src.contents[index] == L'\\' &&
902+ ps->src.contents[index + 1] == L'\n')
903+ line_continuation(ps, index);
904+}
905+
665906 /* Rewind `ps->index` to `oldindex' and decrease `ps->info->lineno' accordingly.
907+ * Note that `ps->next_index' is not updated in this function.
666908 *
667909 * You MUST use this function when rewinding the index in order to correctly
668910 * rewind the line number. The following pattern of code does not work because
@@ -687,35 +929,6 @@
687929 }
688930 }
689931
690-/* If a line continuation is found within `n' characters from the current
691- * position `ps->index', removes the backslash-newline pair and reads the next
692- * line.
693- * If a backslash that is not a line continuation is found within `n' characters
694- * from the current position, this function does nothing. */
695-/* For quickness, `n' should be as small as possible. */
696-void ensure_buffer(parsestate_T *ps, size_t n)
697-{
698- size_t index = ps->index;
699- if (ps->src.contents[index] == L'\0')
700- read_more_input(ps);
701- while (index - ps->index < n) {
702- switch (ps->src.contents[index]) {
703- case L'\0': case L'\'':
704- return;
705- case L'\\':
706- if (ps->src.contents[index + 1] != L'\n')
707- return;
708- line_continuation(ps, index);
709- if (ps->info->lastinputresult != INPUT_OK)
710- return;
711- break;
712- default:
713- index++;
714- break;
715- }
716- }
717-}
718-
719932 /* Returns the length of the name at the current position.
720933 * Whether a character can be part of the name is determined by `isnamechar'.
721934 * This function processes line continuations and reads so many lines that the
@@ -722,725 +935,140 @@
722935 * variable/alias name under the current position is fully available. */
723936 size_t count_name_length(parsestate_T *ps, bool isnamechar(wchar_t c))
724937 {
725- size_t saveindex = ps->index;
726- while (ensure_buffer(ps, 1), isnamechar(ps->src.contents[ps->index]))
727- ps->index++;
728-
729- size_t result = ps->index - saveindex;
730- rewind_index(ps, saveindex);
731- return result;
938+ size_t index = ps->index;
939+ while (maybe_line_continuations(ps, index),
940+ isnamechar(ps->src.contents[index]))
941+ index++;
942+ return index - ps->index;
732943 }
733944
734-/* Advances the current position `ps->index', skipping blank characters,
735- * comments, and line continuations.
736- * This function calls `read_more_input' if the current line has not been read
737- * or when a line continuation is encountered.
738- * The current position is advanced to the next non-blank character.
739- * Line continuations are actually removed rather than skipped. */
740-/* Note that a newline is not a blank character. After a comment was skipped,
741- * the position will be at the newline character (or EOF) that follows. */
742-void skip_blanks_and_comment(parsestate_T *ps)
743-{
744- if (ps->src.contents[ps->index] == L'\0')
745- if (read_more_input(ps) != INPUT_OK)
746- return;
945+/***** Tokenizer *****/
747946
748-start:
749- /* skip blanks */
750- while (iswblank(ps->src.contents[ps->index]))
751- ps->index++;
752-
753- /* skip a comment */
754- if (ps->src.contents[ps->index] == L'#') {
755- do {
756- ps->index++;
757- } while (ps->src.contents[ps->index] != L'\n' &&
758- ps->src.contents[ps->index] != L'\0');
759- }
760-
761- /* remove line continuation */
762- if (ps->src.contents[ps->index] == L'\\' &&
763- ps->src.contents[ps->index + 1] == L'\n') {
764- line_continuation(ps, ps->index);
765- goto start;
766- }
767-}
768-
769-/* Advances the current position `ps->index', skipping blank characters,
770- * comments and newlines, up to the next token.
771- * This function calls `read_more_input' if the next token cannot be found in
772- * the current line.
773- * Returns true iff at least one newline token is skipped. */
774-bool skip_to_next_token(parsestate_T *ps)
947+/* Moves to the next token, updating `index', `next_index', `tokentype', and
948+ * `token' of the parse state.
949+ * The existing `token' is freed. */
950+void next_token(parsestate_T *ps)
775951 {
776- bool newline = false;
952+ wordfree(ps->token);
953+ ps->token = NULL;
777954
778- skip_blanks_and_comment(ps);
779- while (ps->info->lastinputresult == INPUT_OK &&
780- ps->src.contents[ps->index] == L'\n') {
781- newline = true;
782- next_line(ps);
783- skip_blanks_and_comment(ps);
784- }
785- return newline;
786-}
955+ size_t index = ps->next_index;
956+ if (index == ps->src.length)
957+ read_more_input(ps);
787958
788-/* Parses the newline token at the current position and proceeds to the next
789- * line. The contents of pending here-documents are read if any. */
790-void next_line(parsestate_T *ps)
791-{
792- assert(ps->src.contents[ps->index] == L'\n');
793- ps->index++;
794- ps->info->lineno++;
959+skip_blanks:
960+ while (iswblank(ps->src.contents[index]))
961+ index++;
795962
796- for (size_t i = 0; i < ps->pending_heredocs.length; i++)
797- read_heredoc_contents(ps, ps->pending_heredocs.contents[i]);
798- pl_clear(&ps->pending_heredocs, 0);
799-}
800-
801-/* Checks if the specified character is a token separator. */
802-bool is_token_delimiter_char(wchar_t c)
803-{
804- switch (c) {
805- case L'\0': case L'\n': case L';': case L'&': case L'|':
806- case L'<': case L'>': case L'(': case L')':
807- return true;
808- default:
809- return iswblank(c);
963+ if (ps->src.contents[index] == L'\\' &&
964+ ps->src.contents[index + 1] == L'\n') {
965+ line_continuation(ps, index);
966+ goto skip_blanks;
810967 }
811-}
812968
813-/* Checks if the specified character delimits a simple command. */
814-bool is_command_delimiter_char(wchar_t c)
815-{
816- switch (c) {
817- case L'\0': case L'\n': case L';': case L'&': case L'|':
818- case L'(': case L')':
819- return true;
820- default:
821- return false;
822- }
823-}
969+ /* skip any comment */
970+ if (ps->src.contents[index] == L'#')
971+ index += wcscspn(&ps->src.contents[index + 1], L"\n") + 1;
824972
825-bool is_comma_or_closing_bracket(wchar_t c)
826-{
827- return c == L']' || c == L',';
828-}
973+ size_t startindex = index;
829974
830-bool is_slash_or_closing_brace(wchar_t c)
831-{
832- return c == L'/' || c == L'}';
833-}
834-
835-bool is_closing_brace(wchar_t c)
836-{
837- return c == L'}';
838-}
839-
840-/* Checks if token `t' exists at the current position in `ps->src'.
841- * `t' must not be a proper substring of another operator token. (For example,
842- * `t' cannot be L"&" because it is a proper substring of another operator token
843- * L"&&". However, L"do" is OK for `t' even though it is a substring of the
844- * keyword L"done", which is not an operator token.)
845- * This function does not handle line continuations. The caller may need to call
846- * `ensure_buffer(wcslen(t))' before calling this function. */
847-bool has_token(const parsestate_T *ps, const wchar_t *t)
848-{
849- const wchar_t *c = matchwcsprefix(&ps->src.contents[ps->index], t);
850- return c != NULL && is_token_delimiter_char(*c);
851-}
852-
853-/* Checks if there is an 'opening' token such as "(", "{", and "if" at the
854- * current position. If there is one, the token string is returned.
855- * Otherwise, NULL is returned.
856- * This function calls `ensure_buffer(ps, 9)'. */
857-const wchar_t *check_opening_token(parsestate_T *ps)
858-{
859- ensure_buffer(ps, 9);
860- if (ps->src.contents[ps->index] == L'(') return L"(";
861- if (has_token(ps, L"{")) return L"{";
862- if (has_token(ps, L"if")) return L"if";
863- if (has_token(ps, L"for")) return L"for";
864- if (has_token(ps, L"while")) return L"while";
865- if (has_token(ps, L"until")) return L"until";
866- if (has_token(ps, L"case")) return L"case";
867- if (has_token(ps, L"function")) return L"function";
868- return NULL;
869-}
870-
871-/* Checks if there is a 'closing' token such as ")", "}", and "fi" at the
872- * current position. If there is one, the token string is returned.
873- * Otherwise, NULL is returned.
874- * This function calls `ensure_buffer(ps, 5)'. */
875-/* Closing tokens delimit and/or lists. */
876-const wchar_t *check_closing_token(parsestate_T *ps)
877-{
878- ensure_buffer(ps, 5);
879- if (ps->src.contents[ps->index] == L')')
880- return L")";
881- if (ps->src.contents[ps->index] == L';' &&
882- ps->src.contents[ps->index + 1] == L';')
883- return L";;";
884- if (has_token(ps, L"}")) return L"}";
885- if (has_token(ps, L"then")) return L"then";
886- if (has_token(ps, L"else")) return L"else";
887- if (has_token(ps, L"elif")) return L"elif";
888- if (has_token(ps, L"fi")) return L"fi";
889- if (has_token(ps, L"do")) return L"do";
890- if (has_token(ps, L"done")) return L"done";
891- if (has_token(ps, L"esac")) return L"esac";
892- return NULL;
893-}
894-
895-/* Performs alias substitution with the given parse state. */
896-bool psubstitute_alias(parsestate_T *ps, substaliasflags_T flags)
897-{
898- if (!ps->enable_alias)
899- return false;
900-
901- size_t len = count_name_length(ps, is_alias_name_char);
902- return substitute_alias_range(
903- &ps->src, ps->index, ps->index + len, &ps->aliases, flags);
904-}
905-
906-/* Performs alias substitution recursively. This should not be used where the
907- * substitution result may be recognized as a keyword, since keywords should not
908- * be alias-substituted. */
909-void psubstitute_alias_recursive(parsestate_T *ps, substaliasflags_T flags)
910-{
911- while (psubstitute_alias(ps, flags))
912- skip_blanks_and_comment(ps);
913-}
914-
915-/* Parses commands.
916- * If `toeol' is true, commands are parsed up to the end of the current input;
917- * otherwise, up to the next closing token.
918- * You don't have to call `skip_blanks_and_comment' beforehand. */
919-and_or_T *parse_command_list(parsestate_T *ps, bool toeol)
920-{
921- and_or_T *first = NULL, **lastp = &first;
922- bool saveerror = ps->error;
923- bool need_separator = false;
924- /* For a command to be parsed after another, it must be separated by L"&",
925- * L";", or newlines. */
926-
927- if (!toeol && !ps->info->interactive)
928- ps->error = false;
929- while (!ps->error) {
930- if (toeol) {
931- skip_blanks_and_comment(ps);
932- if (ps->src.contents[ps->index] == L'\n') {
933- next_line(ps);
934- need_separator = false;
935- if (ps->src.contents[ps->index] != L'\0')
936- continue;
975+ switch (ps->src.contents[index]) {
976+ case L'\0': ps->tokentype = TT_END_OF_INPUT; break;
977+ case L'\n': ps->tokentype = TT_NEWLINE; index++; break;
978+ case L'(': ps->tokentype = TT_LPAREN; index++; break;
979+ case L')': ps->tokentype = TT_RPAREN; index++; break;
980+ case L';':
981+ maybe_line_continuations(ps, ++index);
982+ if (ps->src.contents[index] == L';') {
983+ ps->tokentype = TT_DOUBLE_SEMICOLON;
984+ index++;
985+ } else {
986+ ps->tokentype = TT_SEMICOLON;
937987 }
938- if (ps->src.contents[ps->index] == L'\0') {
939- break;
940- } else if (ps->src.contents[ps->index] == L')') {
941- serror(ps, get_errmsg_unexpected_token(L")"), L")");
942- break;
943- } else if (need_separator) {
944- serror(ps, Ngt("`;' or `&' is missing"));
945- break;
946- }
947- } else {
948- if (skip_to_next_token(ps))
949- need_separator = false;
950- if (need_separator
951- || ps->src.contents[ps->index] == L'\0'
952- || check_closing_token(ps))
953- break;
954- }
955-
956- and_or_T *ao = parse_and_or_list(ps);
957- if (ao != NULL) {
958- *lastp = ao;
959- lastp = &ao->next;
960- }
961- if (ps->reparse) {
962- assert(ao == NULL);
963- continue;
964- }
965-
966- need_separator = true;
967- ensure_buffer(ps, 2);
968- if (ps->src.contents[ps->index] == L'&'
969- || (ps->src.contents[ps->index] == L';'
970- && ps->src.contents[ps->index + 1] != L';')) {
971- ps->index++;
972- need_separator = false;
973- }
974- }
975- if (!toeol)
976- ps->error |= saveerror;
977- ps->reparse = false;
978- return first;
979-}
980-
981-/* Parses commands until a closing token is found.
982- * You don't have to call `skip_blanks_and_comment' beforehand. */
983-and_or_T *parse_compound_list(parsestate_T *ps)
984-{
985- return parse_command_list(ps, false);
986-}
987-
988-/* Parses one and/or list.
989- * The result reflects the trailing "&" or ";", but `ps->index' points to the
990- * delimiter "&" or ";" when the function returns.
991- * If the first word was alias-substituted, the `ps->reparse' flag is set and
992- * NULL is returned. */
993-and_or_T *parse_and_or_list(parsestate_T *ps)
994-{
995- pipeline_T *p = parse_pipelines_in_and_or(ps);
996- if (ps->reparse) {
997- assert(p == NULL);
998- return NULL;
999- }
1000-
1001- and_or_T *result = xmalloc(sizeof *result);
1002- result->next = NULL;
1003- result->ao_pipelines = p;
1004- result->ao_async = (ps->src.contents[ps->index] == L'&');
1005- return result;
1006-}
1007-
1008-/* Parses all pipelines in one and/or list.
1009- * If the first word was alias-substituted, the `ps->reparse' flag is set and
1010- * NULL is returned. */
1011-pipeline_T *parse_pipelines_in_and_or(parsestate_T *ps)
1012-{
1013- pipeline_T *first = NULL, **lastp = &first;
1014- bool cond = false;
1015-
1016- for (;;) {
1017- pipeline_T *p = parse_pipeline(ps);
1018- if (p != NULL) {
1019- p->pl_cond = cond;
1020- *lastp = p;
1021- lastp = &p->next;
1022- }
1023- if (ps->reparse) {
1024- assert(p == NULL);
1025- if (first != NULL)
1026- goto next;
1027- else
1028- break;
1029- }
1030-
1031- ensure_buffer(ps, 2);
1032- if (ps->src.contents[ps->index] == L'&'
1033- && ps->src.contents[ps->index + 1] == L'&') {
1034- cond = true;
1035- } else if (ps->src.contents[ps->index] == L'|'
1036- && ps->src.contents[ps->index + 1] == L'|') {
1037- cond = false;
1038- } else {
1039988 break;
1040- }
1041- ps->index += 2;
1042-next:
1043- skip_to_next_token(ps);
1044- }
1045- return first;
1046-}
1047-
1048-/* Parses one pipeline.
1049- * If the first word was alias-substituted, the `ps->reparse' flag is set and
1050- * NULL is returned. */
1051-pipeline_T *parse_pipeline(parsestate_T *ps)
1052-{
1053- bool neg;
1054- command_T *c;
1055-
1056- ensure_buffer(ps, 2);
1057- if (has_token(ps, L"!")) {
1058- neg = true;
1059- ps->index += 1;
1060- if (posixly_correct && ps->src.contents[ps->index] == L'(')
1061- serror(ps, Ngt("ksh-like extended glob pattern `!(...)' "
1062- "is not supported"));
1063- do {
1064- skip_blanks_and_comment(ps);
1065- c = parse_commands_in_pipeline(ps);
1066- if (ps->reparse)
1067- assert(c == NULL);
1068- } while (ps->reparse);
1069- } else {
1070- neg = false;
1071- c = parse_commands_in_pipeline(ps);
1072- if (ps->reparse) {
1073- assert(c == NULL);
1074- return NULL;
1075- }
1076- }
1077-
1078- pipeline_T *result = xmalloc(sizeof *result);
1079- result->next = NULL;
1080- result->pl_commands = c;
1081- result->pl_neg = neg;
1082- result->pl_cond = false;
1083- return result;
1084-}
1085-
1086-/* Parses the body of the pipeline.
1087- * If the first word was alias-substituted, the `ps->reparse' flag is set and
1088- * NULL is returned. */
1089-command_T *parse_commands_in_pipeline(parsestate_T *ps)
1090-{
1091- command_T *first = NULL, **lastp = &first;
1092-
1093- for (;;) {
1094- command_T *c = parse_command(ps);
1095- if (c != NULL) {
1096- *lastp = c;
1097- lastp = &c->next;
1098- }
1099- if (ps->reparse) {
1100- assert(c == NULL);
1101- if (first != NULL)
1102- goto next;
1103- else
1104- break;
1105- }
1106-
1107- ensure_buffer(ps, 2);
1108- if (ps->src.contents[ps->index] == L'|' &&
1109- ps->src.contents[ps->index + 1] != L'|') {
1110- ps->index++;
1111- } else {
1112- break;
1113- }
1114-next:
1115- skip_to_next_token(ps);
1116- }
1117- return first;
1118-}
1119-
1120-/* Parses one command.
1121- * If the first word was alias-substituted, the `ps->reparse' flag is set and
1122- * NULL is returned. */
1123-command_T *parse_command(parsestate_T *ps)
1124-{
1125- ps->reparse = false;
1126-
1127- /* Note: `check_closing_token' calls `ensure_buffer(ps, 5)'. */
1128- const wchar_t *t = check_closing_token(ps);
1129- if (t != NULL) {
1130- serror(ps, get_errmsg_unexpected_token(t), t);
1131- return NULL;
1132- } else if (has_token(ps, L"!")) {
1133- serror(ps, get_errmsg_unexpected_token(L"!"), L"!");
1134- return NULL;
1135- } else if (has_token(ps, L"in")) {
1136- serror(ps, get_errmsg_unexpected_token(L"in"), L"in");
1137- return NULL;
1138- } else if (ps->src.contents[ps->index] == L'(') {
1139- return parse_compound_command(ps, L"(");
1140- } else if (is_command_delimiter_char(ps->src.contents[ps->index])) {
1141- if (ps->src.contents[ps->index] == L'\0' ||
1142- ps->src.contents[ps->index] == L'\n')
1143- serror(ps, Ngt("a command is missing at the end of input"));
1144- else
1145- serror(ps, Ngt("a command is missing before `%lc'"),
1146- (wint_t) ps->src.contents[ps->index]);
1147- return NULL;
1148- }
1149-
1150- t = check_opening_token(ps);
1151- if (t != NULL)
1152- return parse_compound_command(ps, t);
1153-
1154- if (psubstitute_alias(ps, AF_NONGLOBAL)) {
1155- ps->reparse = true;
1156- return NULL;
1157- }
1158-
1159- /* parse as a simple command */
1160- command_T *result = xmalloc(sizeof *result);
1161- result->next = NULL;
1162- result->refcount = 1;
1163- result->c_lineno = ps->info->lineno;
1164- result->c_type = CT_SIMPLE;
1165- redir_T **redirlastp = parse_assignments_and_redirects(ps, result);
1166- result->c_words = parse_words_and_redirects(ps, redirlastp, true);
1167-
1168- return try_reparse_as_function(ps, result);
1169-}
1170-
1171-/* Parses assignments and redirections.
1172- * Tokens but the first one are subject to any-type alias substitution,
1173- * including the word just after the parsed assignments and redirections.
1174- * The results are assigned to `c->c_assigns' and `c->c_redirs'.
1175- * The return value is a pointer to the `next' member of the last resultant
1176- * redirection (redir_T). If no redirections were parsed, the result value is a
1177- * pointer to `c->c_redirs'. */
1178-redir_T **parse_assignments_and_redirects(parsestate_T *ps, command_T *c)
1179-{
1180- assign_T **assgnlastp = &c->c_assigns;
1181- redir_T **redirlastp = &c->c_redirs;
1182- assign_T *assgn;
1183- redir_T *redir;
1184-
1185- c->c_assigns = NULL;
1186- c->c_redirs = NULL;
1187- while (ensure_buffer(ps, 1),
1188- !is_command_delimiter_char(ps->src.contents[ps->index])) {
1189- if ((redir = tryparse_redirect(ps)) != NULL) {
1190- *redirlastp = redir;
1191- redirlastp = &redir->next;
1192- } else if ((assgn = tryparse_assignment(ps)) != NULL) {
1193- *assgnlastp = assgn;
1194- assgnlastp = &assgn->next;
1195- } else {
1196- break;
1197- }
1198- psubstitute_alias_recursive(ps, AF_NONGLOBAL);
1199- }
1200- return redirlastp;
1201-}
1202-
1203-/* Parses words and redirections.
1204- * The parsing result of redirections is assigned to `*redirlastp'
1205- * The parsing result of assignments is returned as an array of pointers to
1206- * word units that are cast to (void *).
1207- * `*redirlastp' must have been initialized to NULL beforehand.
1208- * All words are subject to global alias substitution. If `first' is true,
1209- * however, alias substitution is not performed on the first word. */
1210-void **parse_words_and_redirects(
1211- parsestate_T *ps, redir_T **redirlastp, bool first)
1212-{
1213- plist_T wordlist;
1214- redir_T *redir;
1215- wordunit_T *word;
1216-
1217- assert(*redirlastp == NULL);
1218- pl_init(&wordlist);
1219- while (ensure_buffer(ps, 1),
1220- !is_command_delimiter_char(ps->src.contents[ps->index])) {
1221- if (!first)
1222- psubstitute_alias_recursive(ps, 0);
1223- if ((redir = tryparse_redirect(ps)) != NULL) {
1224- *redirlastp = redir;
1225- redirlastp = &redir->next;
1226- } else if ((word = parse_word(ps, false)) != NULL) {
1227- pl_add(&wordlist, word);
1228- skip_blanks_and_comment(ps);
1229- first = false;
1230- } else {
1231- break;
1232- }
1233- }
1234- return pl_toary(&wordlist);
1235-}
1236-
1237-/* Parses as many redirections as possible.
1238- * The parsing result is assigned to `*redirlastp'
1239- * `*redirlastp' must have been initialized to NULL beforehand. */
1240-void parse_redirect_list(parsestate_T *ps, redir_T **lastp)
1241-{
1242- for (;;) {
1243- psubstitute_alias_recursive(ps, 0);
1244-
1245- redir_T *redir = tryparse_redirect(ps);
1246- if (redir == NULL)
1247- break;
1248- *lastp = redir;
1249- lastp = &redir->next;
1250- }
1251-}
1252-
1253-/* If there is an assignment at the current position, parses and returns it.
1254- * Otherwise, returns NULL without moving the position. */
1255-assign_T *tryparse_assignment(parsestate_T *ps)
1256-{
1257- if (iswdigit(ps->src.contents[ps->index]))
1258- return NULL;
1259-
1260- size_t namelen = count_name_length(ps, is_name_char);
1261- if (namelen == 0 || ps->src.contents[ps->index + namelen] != L'=')
1262- return NULL;
1263-
1264- assign_T *result = xmalloc(sizeof *result);
1265- result->next = NULL;
1266- result->a_name = xwcsndup(&ps->src.contents[ps->index], namelen);
1267- ps->index += namelen + 1;
1268-
1269- ensure_buffer(ps, 1);
1270- if (posixly_correct || ps->src.contents[ps->index] != L'(') {
1271- result->a_type = A_SCALAR;
1272- result->a_scalar = parse_word(ps, false);
1273- } else {
1274- ps->index++;
1275- skip_to_next_token(ps);
1276- result->a_type = A_ARRAY;
1277- result->a_array = parse_words_to_paren(ps);
1278- if (ps->src.contents[ps->index] == L')')
1279- ps->index++;
1280- else
1281- serror(ps, Ngt("`%ls' is missing"), L")");
1282- }
1283- skip_blanks_and_comment(ps);
1284- return result;
1285-}
1286-
1287-/* Parses words until the next closing parentheses.
1288- * Delimiter characters other than ')' and '\n' are not allowed.
1289- * Returns a newly malloced array of pointers to newly malloced `wordunit_T's.*/
1290-void **parse_words_to_paren(parsestate_T *ps)
1291-{
1292- plist_T list;
1293-
1294- pl_init(&list);
1295- while (ps->src.contents[ps->index] != L')') {
1296- wordunit_T *word = parse_word(ps, true);
1297- if (word != NULL)
1298- pl_add(&list, word);
1299- else
1300- break;
1301- skip_to_next_token(ps);
1302- }
1303- return pl_toary(&list);
1304-}
1305-
1306-/* If there is a redirection at the current position, parses and returns it.
1307- * Otherwise, returns NULL without moving the position. */
1308-redir_T *tryparse_redirect(parsestate_T *ps)
1309-{
1310- int fd;
1311-
1312- ensure_buffer(ps, 2);
1313- if (iswdigit(ps->src.contents[ps->index])) {
1314- unsigned long lfd;
1315- wchar_t *endptr;
1316-
1317-reparse:
1318- errno = 0;
1319- lfd = wcstoul(&ps->src.contents[ps->index], &endptr, 10);
1320- if (errno != 0 || lfd > INT_MAX)
1321- fd = -1; /* invalid fd */
1322- else
1323- fd = (int) lfd;
1324- if (endptr[0] == L'\\' && endptr[1] == L'\n') {
1325- line_continuation(ps, endptr - ps->src.contents);
1326- goto reparse;
1327- } else if (endptr[0] != L'<' && endptr[0] != L'>') {
1328- return NULL;
1329- }
1330- ps->index = endptr - ps->src.contents;
1331- } else if (ps->src.contents[ps->index] == L'<') {
1332- fd = STDIN_FILENO;
1333- } else if (ps->src.contents[ps->index] == L'>') {
1334- fd = STDOUT_FILENO;
1335- } else {
1336- return NULL;
1337- }
1338-
1339- redir_T *result = xmalloc(sizeof *result);
1340- result->next = NULL;
1341- result->rd_fd = fd;
1342- ensure_buffer(ps, 3);
1343- switch (ps->src.contents[ps->index]) {
1344- case L'<':
1345- switch (ps->src.contents[ps->index + 1]) {
1346- case L'<':
1347- if (ps->src.contents[ps->index + 2] == L'-') {
1348- result->rd_type = RT_HERERT;
1349- ps->index += 3;
1350- } else if (!posixly_correct &&
1351- ps->src.contents[ps->index + 2] == L'<') {
1352- result->rd_type = RT_HERESTR;
1353- ps->index += 3;
989+ case L'&':
990+ maybe_line_continuations(ps, ++index);
991+ if (ps->src.contents[index] == L'&') {
992+ ps->tokentype = TT_AMPAMP;
993+ index++;
1354994 } else {
1355- result->rd_type = RT_HERE;
1356- ps->index += 2;
995+ ps->tokentype = TT_AMP;
1357996 }
1358997 break;
1359- case L'(':
1360- if (!posixly_correct) {
1361- result->rd_type = RT_PROCIN;
1362- goto parse_command;
998+ case L'|':
999+ maybe_line_continuations(ps, ++index);
1000+ if (ps->src.contents[index] == L'|') {
1001+ ps->tokentype = TT_PIPEPIPE;
1002+ index++;
13631003 } else {
1364- result->rd_type = RT_INPUT;
1365- ps->index += 1;
1004+ ps->tokentype = TT_PIPE;
13661005 }
13671006 break;
1368- case L'>': result->rd_type = RT_INOUT; ps->index += 2; break;
1369- case L'&': result->rd_type = RT_DUPIN; ps->index += 2; break;
1370- default: result->rd_type = RT_INPUT; ps->index += 1; break;
1371- }
1372- break;
1373- case L'>':
1374- switch (ps->src.contents[ps->index + 1]) {
1375- case L'(':
1376- if (!posixly_correct) {
1377- result->rd_type = RT_PROCOUT;
1378- goto parse_command;
1379- } else {
1380- result->rd_type = RT_OUTPUT;
1381- ps->index += 1;
1007+ case L'<':
1008+ maybe_line_continuations(ps, ++index);
1009+ switch (ps->src.contents[index]) {
1010+ default: ps->tokentype = TT_LESS; break;
1011+ case L'>': ps->tokentype = TT_LESSGREATER; index++; break;
1012+ case L'(': ps->tokentype = TT_LESSLPAREN; index++; break;
1013+ case L'&': ps->tokentype = TT_LESSAMP; index++; break;
1014+ case L'<':
1015+ maybe_line_continuations(ps, ++index);
1016+ switch (ps->src.contents[index]) {
1017+ default:
1018+ ps->tokentype = TT_LESSLESS; break;
1019+ case L'-':
1020+ ps->tokentype = TT_LESSLESSDASH; index++; break;
1021+ case L'<':
1022+ ps->tokentype = TT_LESSLESSLESS; index++; break;
1023+ }
1024+ break;
13821025 }
13831026 break;
13841027 case L'>':
1385- if (!posixly_correct && ps->src.contents[ps->index + 2] == L'|') {
1386- result->rd_type = RT_PIPE;
1387- ps->index += 3;
1388- } else {
1389- result->rd_type = RT_APPEND;
1390- ps->index += 2;
1028+ maybe_line_continuations(ps, ++index);
1029+ switch (ps->src.contents[index]) {
1030+ default: ps->tokentype = TT_GREATER; break;
1031+ case L'(': ps->tokentype = TT_GREATERLPAREN; index++; break;
1032+ case L'&': ps->tokentype = TT_GREATERAMP; index++; break;
1033+ case L'|': ps->tokentype = TT_GREATERPIPE; index++; break;
1034+ case L'>':
1035+ maybe_line_continuations(ps, ++index);
1036+ if (ps->src.contents[index] == L'|') {
1037+ ps->tokentype = TT_GREATERGREATERPIPE;
1038+ index++;
1039+ } else {
1040+ ps->tokentype = TT_GREATERGREATER;
1041+ }
1042+ break;
13911043 }
13921044 break;
1393- case L'|': result->rd_type = RT_CLOBBER; ps->index += 2; break;
1394- case L'&': result->rd_type = RT_DUPOUT; ps->index += 2; break;
1395- default: result->rd_type = RT_OUTPUT; ps->index += 1; break;
1396- }
1397- break;
1398- default:
1399- assert(false);
1400- }
1401- skip_blanks_and_comment(ps);
1402- if (result->rd_type != RT_HERE && result->rd_type != RT_HERERT) {
1403- result->rd_filename = parse_word(ps, true);
1404- if (result->rd_filename == NULL) {
1405- serror(ps, Ngt("the redirection target is missing"));
1406- free(result);
1407- return NULL;
1408- }
1409- } else {
1410- wchar_t *endofheredoc = parse_word_as_wcs(ps);
1411- if (endofheredoc[0] == L'\0') {
1412- serror(ps, Ngt("the end-of-here-document indicator is missing"));
1413- free(endofheredoc);
1414- free(result);
1415- return NULL;
1416- }
1417- result->rd_hereend = endofheredoc;
1418- result->rd_herecontent = NULL;
1419- pl_add(&ps->pending_heredocs, result);
1420- }
1421- skip_blanks_and_comment(ps);
1422- return result;
14231045
1424-parse_command:
1425- ps->index += 1;
1426- result->rd_command = extract_command_in_paren(ps);
1427- ensure_buffer(ps, 1);
1428- if (ps->src.contents[ps->index] == L')')
1429- ps->index++;
1430- else
1431- serror(ps, Ngt("unclosed process redirection"));
1432- skip_blanks_and_comment(ps);
1433- return result;
1434-}
1046+ default:
1047+ /* Okay, the next token seems to be a word, possibly being a
1048+ * reserved word or an IO_NUMBER token. */
1049+ ps->index = index;
1050+ wordunit_T *token = parse_word(ps, is_token_delimiter_char);
1051+ index = ps->index;
14351052
1436-/* Parses a word at the current position. If `globalaliases' is true, global
1437- * aliases are substituted before the word is parsed. */
1438-wordunit_T *parse_word(parsestate_T *ps, bool globalaliases)
1439-{
1440- if (globalaliases)
1441- psubstitute_alias_recursive(ps, 0);
1053+ wordfree(ps->token);
1054+ ps->token = token;
14421055
1443- return parse_word_to(ps, is_token_delimiter_char);
1056+ /* Is this an IO_NUMBER token? */
1057+ if (ps->src.contents[index] == L'<' ||
1058+ ps->src.contents[index] == L'>') {
1059+ if (is_digits_only(ps->token)) {
1060+ ps->tokentype = TT_IO_NUMBER;
1061+ break;
1062+ }
1063+ }
1064+
1065+ /* Is this a reserved word? */
1066+ ps->tokentype = identify_reserved_word(ps->token);
1067+ break;
1068+ }
1069+
1070+ ps->index = startindex;
1071+ ps->next_index = index;
14441072 }
14451073
14461074 /* Parses a word at the current position.
@@ -1449,7 +1077,7 @@
14491077 * returns false.
14501078 * It is not an error if there is no characters to be a word, in which case
14511079 * NULL is returned. */
1452-wordunit_T *parse_word_to(parsestate_T *ps, bool testfunc(wchar_t c))
1080+wordunit_T *parse_word(parsestate_T *ps, bool testfunc(wchar_t c))
14531081 {
14541082 wordunit_T *first = NULL, **lastp = &first;
14551083 bool indq = false; /* in double quotes? */
@@ -1470,7 +1098,7 @@
14701098 } \
14711099 } while (0)
14721100
1473- while (ensure_buffer(ps, 1),
1101+ while (maybe_line_continuations(ps, ps->index),
14741102 indq || !testfunc(ps->src.contents[ps->index])) {
14751103
14761104 switch (ps->src.contents[ps->index]) {
@@ -1563,16 +1191,18 @@
15631191 {
15641192 switch (ps->src.contents[ps->index++]) {
15651193 case L'$':
1566- ensure_buffer(ps, 2);
1194+ maybe_line_continuations(ps, ps->index);
15671195 switch (ps->src.contents[ps->index]) {
15681196 case L'{':
15691197 return parse_paramexp_in_brace(ps);
15701198 case L'(':
1199+ maybe_line_continuations(ps, ps->index + 1);
15711200 if (ps->src.contents[ps->index + 1] == L'(') {
15721201 wordunit_T *wu = tryparse_arith(ps);
15731202 if (wu != NULL)
15741203 return wu;
15751204 }
1205+ ps->next_index = ps->index + 1;
15761206 return parse_cmdsubst_in_paren(ps);
15771207 default:
15781208 return tryparse_paramexp_raw(ps);
@@ -1593,7 +1223,7 @@
15931223 paramexp_T *pe;
15941224 size_t namelen; /* parameter name length */
15951225
1596- ensure_buffer(ps, 1);
1226+ maybe_line_continuations(ps, ps->index);
15971227 switch (ps->src.contents[ps->index]) {
15981228 case L'@': case L'*': case L'#': case L'?':
15991229 case L'-': case L'$': case L'!':
@@ -1640,13 +1270,15 @@
16401270 ps->index++;
16411271
16421272 /* parse PT_NUMBER */
1643- ensure_buffer(ps, 3);
1273+ maybe_line_continuations(ps, ps->index);
16441274 if (ps->src.contents[ps->index] == L'#') {
1275+ maybe_line_continuations(ps, ps->index + 1);
16451276 switch (ps->src.contents[ps->index + 1]) {
16461277 case L'\0': case L'}':
16471278 case L'+': case L'=': case L':': case L'/': case L'%':
16481279 break;
16491280 case L'-': case L'?': case L'#':
1281+ maybe_line_continuations(ps, ps->index + 2);
16501282 if (ps->src.contents[ps->index + 2] != L'}')
16511283 break;
16521284 /* falls thru! */
@@ -1658,7 +1290,7 @@
16581290 }
16591291
16601292 /* parse nested expansion */
1661- // ensure_buffer(2); // we've already called `ensure_buffer'
1293+ // maybe_line_continuations(ps, ps->index); // already called above
16621294 if (!posixly_correct && ps->src.contents[ps->index] == L'{') {
16631295 pe->pe_type |= PT_NEST;
16641296 pe->pe_nest = parse_paramexp_in_brace(ps);
@@ -1665,14 +1297,15 @@
16651297 } else if (!posixly_correct
16661298 && (ps->src.contents[ps->index] == L'`'
16671299 || (ps->src.contents[ps->index] == L'$'
1668- && (ps->src.contents[ps->index + 1] == L'{'
1300+ && (maybe_line_continuations(ps, ps->index + 1),
1301+ ps->src.contents[ps->index + 1] == L'{'
16691302 || ps->src.contents[ps->index + 1] == L'(')))) {
16701303 size_t neststartindex = ps->index;
16711304 pe->pe_nest = parse_special_word_unit(ps, false);
1672- if (ps->index != neststartindex)
1673- pe->pe_type |= PT_NEST;
1674- else
1305+ if (ps->index == neststartindex)
16751306 goto parse_name;
1307+ pe->pe_type |= PT_NEST;
1308+ maybe_line_continuations(ps, ps->index);
16761309 } else {
16771310 parse_name:;
16781311 /* no nesting: parse parameter name normally */
@@ -1683,7 +1316,7 @@
16831316 ps->index++;
16841317 break;
16851318 default:
1686- while (ensure_buffer(ps, 1),
1319+ while (maybe_line_continuations(ps, ps->index),
16871320 is_name_char(ps->src.contents[ps->index]))
16881321 ps->index++;
16891322 break;
@@ -1697,32 +1330,34 @@
16971330 }
16981331
16991332 /* parse indices */
1700- ensure_buffer(ps, 3);
1333+ // maybe_line_continuations(ps, ps->index); // already called above
17011334 if (!posixly_correct && ps->src.contents[ps->index] == L'[') {
17021335 ps->index++;
1703- pe->pe_start = parse_word_to(ps, is_comma_or_closing_bracket);
1336+ pe->pe_start = parse_word(ps, is_comma_or_closing_bracket);
17041337 if (pe->pe_start == NULL)
17051338 serror(ps, Ngt("the index is missing"));
17061339 if (ps->src.contents[ps->index] == L',') {
17071340 ps->index++;
1708- pe->pe_end = parse_word_to(ps, is_comma_or_closing_bracket);
1341+ pe->pe_end = parse_word(ps, is_comma_or_closing_bracket);
17091342 if (pe->pe_end == NULL)
17101343 serror(ps, Ngt("the index is missing"));
17111344 }
1712- if (ps->src.contents[ps->index] == L']')
1713- ps->index++;
1714- else
1345+ if (ps->src.contents[ps->index] == L']') {
1346+ maybe_line_continuations(ps, ++ps->index);
1347+ } else {
17151348 serror(ps, Ngt("`%ls' is missing"), L"]");
1716- ensure_buffer(ps, 3);
1349+ }
17171350 }
17181351
17191352 /* parse PT_COLON */
1353+ // maybe_line_continuations(ps, ps->index); // already called above
17201354 if (ps->src.contents[ps->index] == L':') {
17211355 pe->pe_type |= PT_COLON;
1722- ps->index++;
1356+ maybe_line_continuations(ps, ++ps->index);
17231357 }
17241358
17251359 /* parse '-', '+', '#', etc. */
1360+ // maybe_line_continuations(ps, ps->index); // already called above
17261361 switch (ps->src.contents[ps->index]) {
17271362 case L'-': pe->pe_type |= PT_MINUS; goto parse_subst;
17281363 case L'+': pe->pe_type |= PT_PLUS; goto parse_subst;
@@ -1751,6 +1386,7 @@
17511386 }
17521387
17531388 parse_match:
1389+ maybe_line_continuations(ps, ps->index + 1);
17541390 if (pe->pe_type & PT_COLON) {
17551391 if ((pe->pe_type & PT_MASK) == PT_SUBST)
17561392 pe->pe_type |= PT_MATCHHEAD | PT_MATCHTAIL;
@@ -1757,7 +1393,7 @@
17571393 else
17581394 serror(ps, Ngt("invalid use of `%lc' in parameter expansion"),
17591395 (wint_t) L':');
1760- ps->index += 1;
1396+ maybe_line_continuations(ps, ++ps->index);
17611397 } else if (ps->src.contents[ps->index] ==
17621398 ps->src.contents[ps->index + 1]) {
17631399 if ((pe->pe_type & PT_MASK) == PT_MATCH)
@@ -1779,11 +1415,11 @@
17791415 ps->index += 1;
17801416 }
17811417 if ((pe->pe_type & PT_MASK) == PT_MATCH) {
1782- pe->pe_match = parse_word_to(ps, is_closing_brace);
1418+ pe->pe_match = parse_word(ps, is_closing_brace);
17831419 goto check_closing_brace;
17841420 } else {
1785- pe->pe_match = parse_word_to(ps, is_slash_or_closing_brace);
1786- ensure_buffer(ps, 1);
1421+ pe->pe_match = parse_word(ps, is_slash_or_closing_brace);
1422+ // maybe_line_continuations(ps, ps->index); // called in parse_word
17871423 if (ps->src.contents[ps->index] != L'/')
17881424 goto check_closing_brace;
17891425 }
@@ -1790,10 +1426,10 @@
17901426
17911427 parse_subst:
17921428 ps->index++;
1793- pe->pe_subst = parse_word_to(ps, is_closing_brace);
1429+ pe->pe_subst = parse_word(ps, is_closing_brace);
17941430
17951431 check_closing_brace:
1796- ensure_buffer(ps, 1);
1432+ // maybe_line_continuations(ps, ps->index); // already called above
17971433 if (ps->src.contents[ps->index] == L'}')
17981434 ps->index++;
17991435 else
@@ -1811,9 +1447,9 @@
18111447 }
18121448
18131449 /* Parses a command substitution that starts with "$(".
1814- * The current position must be at the opening parenthesis L'(' when this
1815- * function is called and the position is advanced to the closing parenthesis
1816- * L')'. */
1450+ * When this function is called, `ps->next_index' must be just after the opening
1451+ * "(". When this function returns, `ps->index' is just after the closing ")".
1452+ */
18171453 wordunit_T *parse_cmdsubst_in_paren(parsestate_T *ps)
18181454 {
18191455 wordunit_T *result = xmalloc(sizeof *result);
@@ -1821,7 +1457,7 @@
18211457 result->wu_type = WT_CMDSUB;
18221458 result->wu_cmdsub = extract_command_in_paren(ps);
18231459
1824- ensure_buffer(ps, 1);
1460+ maybe_line_continuations(ps, ps->index);
18251461 if (ps->src.contents[ps->index] == L')')
18261462 ps->index++;
18271463 else
@@ -1830,15 +1466,16 @@
18301466 }
18311467
18321468 /* Extracts commands between '(' and ')'.
1833- * The current position must be at the opening parenthesis L'(' when this
1834- * function is called. The position is advanced to the closing parenthesis
1835- * L')'. */
1469+ * When this function is called, `ps->next_index' must be just after the opening
1470+ * "(". When this function returns, the current token will be the closing ")".
1471+ */
18361472 embedcmd_T extract_command_in_paren(parsestate_T *ps)
18371473 {
18381474 plist_T save_pending_heredocs;
18391475 embedcmd_T result;
18401476
1841- assert(ps->src.contents[ps->index] == L'(');
1477+ assert(ps->next_index > 0);
1478+ assert(ps->src.contents[ps->next_index - 1] == L'(');
18421479
18431480 save_pending_heredocs = ps->pending_heredocs;
18441481 pl_init(&ps->pending_heredocs);
@@ -1847,7 +1484,7 @@
18471484 result.is_preparsed = false;
18481485 result.value.unparsed = extract_command_in_paren_unparsed(ps);
18491486 } else {
1850- ps->index++;
1487+ next_token(ps);
18511488 result.is_preparsed = true;
18521489 result.value.preparsed = parse_compound_list(ps);
18531490 }
@@ -1859,15 +1496,15 @@
18591496 }
18601497
18611498 /* Parses commands between '(' and ')'.
1862- * The current position must be at the opening parenthesis L'(' when this
1863- * function is called. The position is advanced to the closing parenthesis
1864- * L')'. */
1499+ * The current token must be the opening parenthesis L'(' when this function is
1500+ * called. The current token is advanced to the closing parenthesis L')'. */
18651501 wchar_t *extract_command_in_paren_unparsed(parsestate_T *ps)
18661502 {
18671503 bool save_enable_alias = ps->enable_alias;
18681504 ps->enable_alias = false;
18691505
1870- size_t startindex = ++ps->index;
1506+ size_t startindex = ps->next_index;
1507+ next_token(ps);
18711508 andorsfree(parse_compound_list(ps));
18721509 assert(startindex <= ps->index);
18731510
@@ -1895,7 +1532,7 @@
18951532 assert(ps->src.contents[ps->index - 1] == L'`');
18961533 wb_init(&buf);
18971534 for (;;) {
1898- ensure_buffer(ps, 1);
1535+ maybe_line_continuations(ps, ps->index);
18991536 switch (ps->src.contents[ps->index]) {
19001537 case L'\0':
19011538 serror(ps,
@@ -1948,10 +1585,11 @@
19481585 int nestparen = 0;
19491586
19501587 for (;;) {
1951- ensure_buffer(ps, 1);
1588+ maybe_line_continuations(ps, ps->index);
19521589 switch (ps->src.contents[ps->index]) {
19531590 case L'\0':
1954- goto fail;
1591+ serror(ps, Ngt("`%ls' is missing"), L"))");
1592+ goto end;
19551593 case L'\\':
19561594 if (ps->src.contents[ps->index + 1] != L'\0') {
19571595 assert(ps->src.contents[ps->index + 1] != L'\n');
@@ -1980,23 +1618,26 @@
19801618 break;
19811619 case L')':
19821620 nestparen--;
1983- if (nestparen < 0) {
1984- ensure_buffer(ps, 2);
1985- if (ps->src.contents[ps->index + 1] == L')')
1621+ if (nestparen >= 0)
1622+ break;
1623+ maybe_line_continuations(ps, ps->index + 1);
1624+ switch (ps->src.contents[ps->index + 1]) {
1625+ case L')':
1626+ MAKE_WORDUNIT_STRING;
1627+ ps->index += 2;
19861628 goto end;
1987- else
1988- goto fail;
1629+ case L'\0':
1630+ serror(ps, Ngt("`%ls' is missing"), L")");
1631+ goto end;
1632+ default:
1633+ goto not_arithmetic_expansion;
19891634 }
1990- break;
19911635 default:
19921636 break;
19931637 }
19941638 ps->index++;
19951639 }
1996-end:
1997- MAKE_WORDUNIT_STRING;
1998- ps->index += 2;
1999-
1640+end:;
20001641 wordunit_T *result = xmalloc(sizeof *result);
20011642 result->next = NULL;
20021643 result->wu_type = WT_ARITH;
@@ -2003,90 +1644,669 @@
20031644 result->wu_arith = first;
20041645 return result;
20051646
2006-fail:
1647+not_arithmetic_expansion:
20071648 wordfree(first);
20081649 rewind_index(ps, saveindex);
20091650 return NULL;
20101651 }
20111652
2012-/* Returns a word token at the current index as a newly malloced string.
2013- * The current position is advanced to the character that just follows the word.
2014- * This function never returns NULL, but may return an empty string. */
2015-wchar_t *parse_word_as_wcs(parsestate_T *ps)
1653+/***** Newline token parser *****/
1654+
1655+/* Parses the newline token at the current position and proceeds to the next
1656+ * line. The contents of pending here-documents are read if any. The current
1657+ * token is cleared. */
1658+void next_line(parsestate_T *ps)
20161659 {
1660+ assert(ps->src.contents[ps->index] == L'\n');
1661+ ps->index++;
1662+ ps->info->lineno++;
1663+
1664+ for (size_t i = 0; i < ps->pending_heredocs.length; i++)
1665+ read_heredoc_contents(ps, ps->pending_heredocs.contents[i]);
1666+ pl_clear(&ps->pending_heredocs, 0);
1667+
1668+ wordfree(ps->token);
1669+ ps->token = NULL;
1670+ ps->tokentype = TT_UNKNOWN;
1671+ ps->next_index = ps->index;
1672+}
1673+
1674+/* Processes a sequence of newline tokens. Returns true if at least one newline
1675+ * token has been processed; false if none. */
1676+bool parse_newline_list(parsestate_T *ps)
1677+{
1678+ bool found = false;
1679+ while (ps->tokentype == TT_NEWLINE) {
1680+ found = true;
1681+ next_line(ps);
1682+ next_token(ps);
1683+ }
1684+ return found;
1685+}
1686+
1687+/***** Character classifiers *****/
1688+
1689+/* Checks if the specified character is a token separator. */
1690+bool is_token_delimiter_char(wchar_t c)
1691+{
1692+ switch (c) {
1693+ case L'\0': case L'\n': case L';': case L'&': case L'|':
1694+ case L'<': case L'>': case L'(': case L')':
1695+ return true;
1696+ default:
1697+ return iswblank(c);
1698+ }
1699+}
1700+
1701+bool is_comma_or_closing_bracket(wchar_t c)
1702+{
1703+ return c == L']' || c == L',';
1704+}
1705+
1706+bool is_slash_or_closing_brace(wchar_t c)
1707+{
1708+ return c == L'/' || c == L'}';
1709+}
1710+
1711+bool is_closing_brace(wchar_t c)
1712+{
1713+ return c == L'}';
1714+}
1715+
1716+/***** Aliases *****/
1717+
1718+/* Performs alias substitution with the given parse state. Proceeds to the
1719+ * next token if substitution occurred. This function does not substitute an
1720+ * IO_NUMBER token, but do a keyword token. */
1721+bool psubstitute_alias(parsestate_T *ps, substaliasflags_T flags)
1722+{
1723+ if (!ps->enable_alias)
1724+ return false;
1725+ if (ps->tokentype == TT_IO_NUMBER)
1726+ return false;
1727+ if (!is_single_string_word(ps->token))
1728+ return false;
1729+
1730+ bool substituted = substitute_alias_range(
1731+ &ps->src, ps->index, ps->next_index, &ps->aliases, flags);
1732+ if (substituted) {
1733+ /* parse the result of the substitution. */
1734+ ps->next_index = ps->index;
1735+ next_token(ps);
1736+ }
1737+ return substituted;
1738+}
1739+
1740+/* Performs alias substitution recursively. This should not be used where the
1741+ * substitution result may be recognized as a keyword, since keywords should not
1742+ * be alias-substituted. */
1743+void psubstitute_alias_recursive(parsestate_T *ps, substaliasflags_T flags)
1744+{
1745+ while (psubstitute_alias(ps, flags)) ;
1746+}
1747+
1748+/***** Syntax parser functions *****/
1749+
1750+/* Parses commands.
1751+ * If `toeol' is true, commands are parsed up to the end of the current input;
1752+ * otherwise, up to the next closing token. */
1753+and_or_T *parse_command_list(parsestate_T *ps, bool toeol)
1754+{
1755+ and_or_T *first = NULL, **lastp = &first;
1756+ bool saveerror = ps->error;
1757+ bool need_separator = false;
1758+ /* For a command to be parsed after another, it must be separated by L"&",
1759+ * L";", or newlines. */
1760+
1761+ if (!toeol && !ps->info->interactive)
1762+ ps->error = false;
1763+ if (ps->tokentype == TT_UNKNOWN)
1764+ next_token(ps);
1765+
1766+ while (!ps->error) {
1767+ if (toeol) {
1768+ if (ps->tokentype == TT_NEWLINE) {
1769+ next_line(ps);
1770+ need_separator = false;
1771+ if (ps->next_index != ps->src.length) {
1772+ next_token(ps);
1773+ continue;
1774+ }
1775+ wordfree(ps->token);
1776+ ps->token = NULL;
1777+ ps->index = ps->next_index;
1778+ ps->tokentype = TT_END_OF_INPUT;
1779+ }
1780+ if (ps->tokentype == TT_END_OF_INPUT) {
1781+ break;
1782+ } else if (ps->tokentype == TT_RPAREN) {
1783+ print_errmsg_token_unexpected(ps);
1784+ break;
1785+ } else if (need_separator) {
1786+ serror(ps, Ngt("`;' or `&' is missing"));
1787+ break;
1788+ }
1789+ } else {
1790+ if (parse_newline_list(ps))
1791+ need_separator = false;
1792+ if (need_separator || ps->tokentype == TT_END_OF_INPUT ||
1793+ is_closing_tokentype(ps->tokentype))
1794+ break;
1795+ }
1796+
1797+ and_or_T *ao = parse_and_or_list(ps);
1798+ if (ao != NULL) {
1799+ *lastp = ao;
1800+ lastp = &ao->next;
1801+ }
1802+ if (ps->reparse) {
1803+ assert(ao == NULL);
1804+ continue;
1805+ }
1806+
1807+ if (ps->tokentype != TT_AMP && ps->tokentype != TT_SEMICOLON) {
1808+ need_separator = true;
1809+ } else {
1810+ need_separator = false;
1811+ next_token(ps);
1812+ }
1813+ }
1814+ if (!toeol)
1815+ ps->error |= saveerror;
1816+ ps->reparse = false;
1817+ return first;
1818+}
1819+
1820+/* Parses commands until a closing token is found. */
1821+and_or_T *parse_compound_list(parsestate_T *ps)
1822+{
1823+ return parse_command_list(ps, false);
1824+}
1825+
1826+/* Parses one and/or list.
1827+ * The result reflects the trailing "&" or ";", but `ps->index' points to the
1828+ * delimiter "&" or ";" when the function returns.
1829+ * If the first word was alias-substituted, the `ps->reparse' flag is set and
1830+ * NULL is returned. */
1831+and_or_T *parse_and_or_list(parsestate_T *ps)
1832+{
1833+ pipeline_T *p = parse_pipelines_in_and_or(ps);
1834+ if (ps->reparse) {
1835+ assert(p == NULL);
1836+ return NULL;
1837+ }
1838+
1839+ and_or_T *result = xmalloc(sizeof *result);
1840+ result->next = NULL;
1841+ result->ao_pipelines = p;
1842+ result->ao_async = (ps->tokentype == TT_AMP);
1843+ return result;
1844+}
1845+
1846+/* Parses all pipelines in one and/or list.
1847+ * If the first word was alias-substituted, the `ps->reparse' flag is set and
1848+ * NULL is returned. */
1849+pipeline_T *parse_pipelines_in_and_or(parsestate_T *ps)
1850+{
1851+ pipeline_T *first = NULL, **lastp = &first;
1852+ bool cond = false;
1853+
1854+ for (;;) {
1855+ pipeline_T *p = parse_pipeline(ps);
1856+ if (p != NULL) {
1857+ p->pl_cond = cond;
1858+ *lastp = p;
1859+ lastp = &p->next;
1860+ }
1861+ if (ps->reparse) {
1862+ assert(p == NULL);
1863+ if (first != NULL)
1864+ goto next;
1865+ else
1866+ break;
1867+ }
1868+
1869+ if (ps->tokentype == TT_AMPAMP)
1870+ cond = true;
1871+ else if (ps->tokentype == TT_PIPEPIPE)
1872+ cond = false;
1873+ else
1874+ break;
1875+ next_token(ps);
1876+next:
1877+ parse_newline_list(ps);
1878+ }
1879+ return first;
1880+}
1881+
1882+/* Parses one pipeline.
1883+ * If the first word was alias-substituted, the `ps->reparse' flag is set and
1884+ * NULL is returned. */
1885+pipeline_T *parse_pipeline(parsestate_T *ps)
1886+{
1887+ bool neg;
1888+ command_T *c;
1889+
1890+ if (ps->tokentype == TT_BANG) {
1891+ neg = true;
1892+ if (posixly_correct && ps->src.contents[ps->next_index] == L'(')
1893+ serror(ps, Ngt("ksh-like extended glob pattern `!(...)' "
1894+ "is not supported"));
1895+ next_token(ps);
1896+ do {
1897+ c = parse_commands_in_pipeline(ps);
1898+ if (ps->reparse)
1899+ assert(c == NULL);
1900+ } while (ps->reparse);
1901+ } else {
1902+ neg = false;
1903+ c = parse_commands_in_pipeline(ps);
1904+ if (ps->reparse) {
1905+ assert(c == NULL);
1906+ return NULL;
1907+ }
1908+ }
1909+
1910+ pipeline_T *result = xmalloc(sizeof *result);
1911+ result->next = NULL;
1912+ result->pl_commands = c;
1913+ result->pl_neg = neg;
1914+ result->pl_cond = false;
1915+ return result;
1916+}
1917+
1918+/* Parses the body of the pipeline.
1919+ * If the first word was alias-substituted, the `ps->reparse' flag is set and
1920+ * NULL is returned. */
1921+command_T *parse_commands_in_pipeline(parsestate_T *ps)
1922+{
1923+ command_T *first = NULL, **lastp = &first;
1924+
1925+ for (;;) {
1926+ command_T *c = parse_command(ps);
1927+ if (c != NULL) {
1928+ *lastp = c;
1929+ lastp = &c->next;
1930+ }
1931+ if (ps->reparse) {
1932+ assert(c == NULL);
1933+ if (first != NULL)
1934+ goto next;
1935+ else
1936+ break;
1937+ }
1938+
1939+ if (ps->tokentype != TT_PIPE)
1940+ break;
1941+
1942+ next_token(ps);
1943+next:
1944+ parse_newline_list(ps);
1945+ }
1946+ return first;
1947+}
1948+
1949+/* Parses one command.
1950+ * If the first word was alias-substituted, the `ps->reparse' flag is set and
1951+ * NULL is returned. */
1952+command_T *parse_command(parsestate_T *ps)
1953+{
1954+ ps->reparse = false;
1955+
1956+ if (ps->tokentype == TT_BANG || ps->tokentype == TT_IN ||
1957+ is_closing_tokentype(ps->tokentype)) {
1958+ print_errmsg_token_unexpected(ps);
1959+ return NULL;
1960+ }
1961+
1962+ command_T *result = parse_compound_command(ps);
1963+ if (result != NULL)
1964+ return result;
1965+
1966+ if (psubstitute_alias(ps, AF_NONGLOBAL)) {
1967+ ps->reparse = true;
1968+ return NULL;
1969+ }
1970+
1971+ /* parse as a simple command */
1972+ result = xmalloc(sizeof *result);
1973+ result->next = NULL;
1974+ result->refcount = 1;
1975+ result->c_lineno = ps->info->lineno;
1976+ result->c_type = CT_SIMPLE;
1977+ result->c_assigns = NULL;
1978+ result->c_redirs = NULL;
1979+ result->c_words = parse_simple_command_tokens(
1980+ ps, &result->c_assigns, &result->c_redirs);
1981+
1982+ if (result->c_words[0] == NULL && result->c_assigns == NULL &&
1983+ result->c_redirs == NULL) {
1984+ /* an empty command */
1985+ comsfree(result);
1986+ if (ps->tokentype == TT_END_OF_INPUT || ps->tokentype == TT_NEWLINE)
1987+ serror(ps, Ngt("a command is missing at the end of input"));
1988+ else
1989+ serror(ps, Ngt("a command is missing before `%lc'"),
1990+ (wint_t) ps->src.contents[ps->index]);
1991+ return NULL;
1992+ }
1993+
1994+ return try_reparse_as_function(ps, result);
1995+}
1996+
1997+/* Parses assignments, redirections, and words.
1998+ * Assignments are parsed before words are parsed. Tokens are subject to
1999+ * any-type alias substitution until the first word is parsed, except for the
2000+ * first token which is parsed intact. The other words are subject to global
2001+ * alias substitution. Redirections can appear anywhere.
2002+ * Parsed Assignments and redirections are assigned to `*assigns' and `redirs',
2003+ * respectively. They must have been initialized NULL (or anything) before
2004+ * calling this function. Parsed words are returned as a newly-malloced
2005+ * NULL-terminated array of pointers to newly-malloced wordunit_T's. */
2006+void **parse_simple_command_tokens(
2007+ parsestate_T *ps, assign_T **assigns, redir_T **redirs)
2008+{
2009+ bool is_first = true;
2010+ plist_T words;
2011+ pl_init(&words);
2012+
2013+next:
2014+ if (is_first)
2015+ is_first = false;
2016+ else
2017+ psubstitute_alias_recursive(ps, words.length == 0 ? AF_NONGLOBAL : 0);
2018+
2019+ redir_T *redir = tryparse_redirect(ps);
2020+ if (redir != NULL) {
2021+ *redirs = redir;
2022+ redirs = &redir->next;
2023+ goto next;
2024+ }
2025+
2026+ if (words.length == 0) {
2027+ assign_T *assign = tryparse_assignment(ps);
2028+ if (assign != NULL) {
2029+ *assigns = assign;
2030+ assigns = &assign->next;
2031+ goto next;
2032+ }
2033+ }
2034+
2035+ if (ps->token != NULL) {
2036+ pl_add(&words, ps->token), ps->token = NULL;
2037+ next_token(ps);
2038+ goto next;
2039+ }
2040+
2041+ return pl_toary(&words);
2042+}
2043+
2044+/* Parses words.
2045+ * The resultant words are returned as a newly-malloced NULL-terminated array of
2046+ * pointers to word units that are cast to (void *).
2047+ * All words are subject to global alias substitution.
2048+ * If `skip_newlines' is true, newline operators are skipped.
2049+ * Words are parsed until an operator token is found. */
2050+void **parse_words(parsestate_T *ps, bool skip_newlines)
2051+{
2052+ plist_T wordlist;
2053+
2054+ pl_init(&wordlist);
2055+ for (;;) {
2056+ psubstitute_alias_recursive(ps, 0);
2057+ if (skip_newlines && parse_newline_list(ps))
2058+ continue;
2059+ if (ps->token == NULL)
2060+ break;
2061+ pl_add(&wordlist, ps->token), ps->token = NULL;
2062+ next_token(ps);
2063+ }
2064+ return pl_toary(&wordlist);
2065+}
2066+
2067+/* Parses as many redirections as possible.
2068+ * The parsing result is assigned to `*redirlastp'
2069+ * `*redirlastp' must have been initialized to NULL beforehand. */
2070+void parse_redirect_list(parsestate_T *ps, redir_T **lastp)
2071+{
2072+ for (;;) {
2073+ psubstitute_alias_recursive(ps, 0);
2074+
2075+ redir_T *redir = tryparse_redirect(ps);
2076+ if (redir == NULL)
2077+ break;
2078+ *lastp = redir;
2079+ lastp = &redir->next;
2080+ }
2081+}
2082+
2083+/* Re-parses the current token as an assignment word. If successful, the token
2084+ * is consumed and the assignment is returned. For an array assignment, all
2085+ * tokens up to (and including) the closing parenthesis are consumed. If
2086+ * unsuccessful, the current token is not modified and NULL is returned. */
2087+assign_T *tryparse_assignment(parsestate_T *ps)
2088+{
2089+ if (ps->token == NULL)
2090+ return NULL;
2091+ if (ps->token->wu_type != WT_STRING)
2092+ return NULL;
2093+
2094+ const wchar_t *nameend = skip_name(ps->token->wu_string, is_name_char);
2095+ size_t namelen = nameend - ps->token->wu_string;
2096+ if (namelen == 0 || *nameend != L'=')
2097+ return NULL;
2098+
2099+ assign_T *result = xmalloc(sizeof *result);
2100+ result->next = NULL;
2101+ result->a_name = xwcsndup(ps->token->wu_string, namelen);
2102+
2103+ /* remove the name and '=' from the token */
2104+ size_t index_after_first_token = ps->next_index;
2105+ wordunit_T *first_token = ps->token;
2106+ ps->token = NULL;
2107+ wmemmove(first_token->wu_string, &nameend[1], wcslen(&nameend[1]) + 1);
2108+ if (first_token->wu_string[0] == L'\0') {
2109+ wordunit_T *wu = first_token->next;
2110+ wordunitfree(first_token);
2111+ first_token = wu;
2112+ }
2113+
2114+ next_token(ps);
2115+
2116+ if (posixly_correct || first_token != NULL ||
2117+ ps->index != index_after_first_token ||
2118+ ps->tokentype != TT_LPAREN) {
2119+ /* scalar assignment */
2120+ result->a_type = A_SCALAR;
2121+ result->a_scalar = first_token;
2122+ } else {
2123+ /* array assignment */
2124+ next_token(ps);
2125+ result->a_type = A_ARRAY;
2126+ result->a_array = parse_words(ps, true);
2127+ if (ps->tokentype == TT_RPAREN)
2128+ next_token(ps);
2129+ else
2130+ serror(ps, Ngt("`%ls' is missing"), L")");
2131+ }
2132+ return result;
2133+}
2134+
2135+/* If there is a redirection at the current position, parses and returns it.
2136+ * Otherwise, returns NULL without moving the position. */
2137+redir_T *tryparse_redirect(parsestate_T *ps)
2138+{
2139+ int fd;
2140+
2141+ if (ps->tokentype == TT_IO_NUMBER) {
2142+ unsigned long lfd;
2143+ wchar_t *endptr;
2144+
2145+ assert(ps->token != NULL);
2146+ assert(ps->token->wu_type == WT_STRING);
2147+ assert(ps->token->next == NULL);
2148+ errno = 0;
2149+ lfd = wcstoul(ps->token->wu_string, &endptr, 10);
2150+ if (errno != 0 || lfd > INT_MAX)
2151+ fd = -1; /* invalid fd */
2152+ else
2153+ fd = (int) lfd;
2154+ assert(*endptr == L'\0');
2155+ next_token(ps);
2156+ } else if (ps->src.contents[ps->index] == L'<') {
2157+ fd = STDIN_FILENO;
2158+ } else if (ps->src.contents[ps->index] == L'>') {
2159+ fd = STDOUT_FILENO;
2160+ } else {
2161+ return NULL;
2162+ }
2163+
2164+ redir_T *result = xmalloc(sizeof *result);
2165+ result->next = NULL;
2166+ result->rd_fd = fd;
2167+ switch (ps->tokentype) {
2168+ case TT_LESS:
2169+ result->rd_type = RT_INPUT;
2170+ break;
2171+ case TT_LESSGREATER:
2172+ result->rd_type = RT_INOUT;
2173+ break;
2174+ case TT_LESSAMP:
2175+ result->rd_type = RT_DUPIN;
2176+ break;
2177+ case TT_GREATER:
2178+ result->rd_type = RT_OUTPUT;
2179+ break;
2180+ case TT_GREATERGREATER:
2181+ result->rd_type = RT_APPEND;
2182+ break;
2183+ case TT_GREATERPIPE:
2184+ result->rd_type = RT_CLOBBER;
2185+ break;
2186+ case TT_GREATERAMP:
2187+ result->rd_type = RT_DUPOUT;
2188+ break;
2189+ case TT_GREATERGREATERPIPE:
2190+ if (posixly_correct)
2191+ serror(ps, Ngt("pipe redirection is not supported "
2192+ "in the POSIXly-correct mode"));
2193+ result->rd_type = RT_PIPE;
2194+ break;
2195+ case TT_LESSLPAREN:
2196+ result->rd_type = RT_PROCIN;
2197+ goto parse_command;
2198+ case TT_GREATERLPAREN:
2199+ result->rd_type = RT_PROCOUT;
2200+ goto parse_command;
2201+ case TT_LESSLESS:
2202+ result->rd_type = RT_HERE;
2203+ goto parse_here_document_tag;
2204+ case TT_LESSLESSDASH:
2205+ result->rd_type = RT_HERERT;
2206+ goto parse_here_document_tag;
2207+ case TT_LESSLESSLESS:
2208+ if (posixly_correct)
2209+ serror(ps, Ngt("here-string is not supported "
2210+ "in the POSIXly-correct mode"));
2211+ result->rd_type = RT_HERESTR;
2212+ break;
2213+ default:
2214+ assert(false);
2215+ }
2216+
2217+ /* parse redirection target file token */
2218+ next_token(ps);
20172219 psubstitute_alias_recursive(ps, 0);
2220+ result->rd_filename = ps->token, ps->token = NULL;
2221+ if (result->rd_filename != NULL)
2222+ next_token(ps);
2223+ else
2224+ serror(ps, Ngt("the redirection target is missing"));
2225+ return result;
20182226
2019- size_t startindex = ps->index;
2020- wordfree(parse_word(ps, false));
2021- assert(startindex <= ps->index);
2022- return xwcsndup(&ps->src.contents[startindex], ps->index - startindex);
2227+parse_here_document_tag:
2228+ next_token(ps);
2229+ psubstitute_alias_recursive(ps, 0);
2230+ result->rd_hereend =
2231+ xwcsndup(&ps->src.contents[ps->index], ps->next_index - ps->index);
2232+ result->rd_herecontent = NULL;
2233+ if (ps->token == NULL) {
2234+ serror(ps, Ngt("the end-of-here-document indicator is missing"));
2235+ } else {
2236+ pl_add(&ps->pending_heredocs, result);
2237+ next_token(ps);
2238+ }
2239+ return result;
2240+
2241+parse_command:
2242+ if (posixly_correct)
2243+ serror(ps, Ngt("process redirection is not supported "
2244+ "in the POSIXly-correct mode"));
2245+ result->rd_command = extract_command_in_paren(ps);
2246+ if (ps->tokentype == TT_RPAREN)
2247+ next_token(ps);
2248+ else
2249+ serror(ps, Ngt("unclosed process redirection"));
2250+ return result;
20232251 }
20242252
20252253 /* Parses a compound command.
2026- * `command' is the name of the command to parse such as "(" and "if". */
2027-command_T *parse_compound_command(parsestate_T *ps, const wchar_t *command)
2254+ * `command' is the name of the command to parse such as "(" and "if".
2255+ * Returns NULL iff the current token does not start a compound command. */
2256+command_T *parse_compound_command(parsestate_T *ps)
20282257 {
2029- /* `parse_group', `parse_if', etc. don't call `skip_blanks_and_comment'
2030- * before they return nor parse redirections. */
20312258 command_T *result;
2032- switch (command[0]) {
2033- case L'(':
2034- result = parse_group(ps, CT_SUBSHELL);
2259+ switch (ps->tokentype) {
2260+ case TT_LPAREN:
2261+ case TT_LBRACE:
2262+ result = parse_group(ps);
20352263 break;
2036- case L'{':
2037- result = parse_group(ps, CT_GROUP);
2038- break;
2039- case L'i':
2264+ case TT_IF:
20402265 result = parse_if(ps);
20412266 break;
2042- case L'f':
2043- switch (command[1]) {
2044- case L'o':
2045- result = parse_for(ps);
2046- break;
2047- case L'u':
2048- result = parse_function(ps);
2049- break;
2050- default:
2051- assert(false);
2052- }
2267+ case TT_FOR:
2268+ result = parse_for(ps);
20532269 break;
2054- case L'w':
2055- result = parse_while(ps, true);
2270+ case TT_FUNCTION:
2271+ result = parse_function(ps);
20562272 break;
2057- case L'u':
2058- result = parse_while(ps, false);
2273+ case TT_WHILE:
2274+ case TT_UNTIL:
2275+ result = parse_while(ps);
20592276 break;
2060- case L'c':
2277+ case TT_CASE:
20612278 result = parse_case(ps);
20622279 break;
20632280 default:
2064- assert(false);
2281+ return NULL;
20652282 }
2066- skip_blanks_and_comment(ps);
20672283 parse_redirect_list(ps, &result->c_redirs);
20682284 return result;
20692285 }
20702286
20712287 /* Parses a command group.
2072- * `type' must be either CT_GROUP or CT_SUBSHELL. */
2073-command_T *parse_group(parsestate_T *ps, commandtype_T type)
2288+ * The current token must be the starting "(" or "{". Never returns NULL. */
2289+command_T *parse_group(parsestate_T *ps)
20742290 {
2075- const wchar_t *start, *end;
2291+ commandtype_T type;
2292+ tokentype_T endtt;
2293+ const wchar_t *starts, *ends;
20762294
2077- switch (type) {
2078- case CT_GROUP:
2079- start = L"{", end = L"}";
2080- assert(has_token(ps, start));
2295+ switch (ps->tokentype) {
2296+ case TT_LBRACE:
2297+ type = CT_GROUP;
2298+ endtt = TT_RBRACE;
2299+ starts = L"{", ends = L"}";
20812300 break;
2082- case CT_SUBSHELL:
2083- start = L"(", end = L")";
2084- assert(ps->src.contents[ps->index] == start[0]);
2301+ case TT_LPAREN:
2302+ type = CT_SUBSHELL;
2303+ endtt = TT_RPAREN;
2304+ starts = L"(", ends = L")";
20852305 break;
20862306 default:
20872307 assert(false);
20882308 }
2089- ps->index++;
2309+ next_token(ps);
20902310
20912311 command_T *result = xmalloc(sizeof *result);
20922312 result->next = NULL;
@@ -2097,19 +2317,20 @@
20972317 result->c_subcmds = parse_compound_list(ps);
20982318 if (posixly_correct && result->c_subcmds == NULL)
20992319 serror(ps, Ngt("commands are missing between `%ls' and `%ls'"),
2100- start, end);
2101- if (ps->src.contents[ps->index] == end[0])
2102- ps->index++;
2320+ starts, ends);
2321+ if (ps->tokentype == endtt)
2322+ next_token(ps);
21032323 else
2104- print_errmsg_token_missing(ps, end);
2324+ print_errmsg_token_missing(ps, ends);
21052325 return result;
21062326 }
21072327
2108-/* Parses a if command */
2328+/* Parses an if command.
2329+ * The current token must be the starting "if". Never returns NULL. */
21092330 command_T *parse_if(parsestate_T *ps)
21102331 {
2111- assert(has_token(ps, L"if"));
2112- ps->index += 2;
2332+ assert(ps->tokentype == TT_IF);
2333+ next_token(ps);
21132334
21142335 command_T *result = xmalloc(sizeof *result);
21152336 result->next = NULL;
@@ -2132,9 +2353,8 @@
21322353 serror(ps, Ngt("commands are missing between `%ls' and `%ls'"),
21332354 (result->c_ifcmds->next == NULL) ? L"if" : L"elif",
21342355 L"then");
2135- ensure_buffer(ps, 5);
2136- if (has_token(ps, L"then"))
2137- ps->index += 4;
2356+ if (ps->tokentype == TT_THEN)
2357+ next_token(ps);
21382358 else
21392359 print_errmsg_token_missing(ps, L"then");
21402360 } else {
@@ -2144,14 +2364,13 @@
21442364 if (posixly_correct && ic->ic_commands == NULL)
21452365 serror(ps, Ngt("commands are missing after `%ls'"),
21462366 after_else ? L"else" : L"then");
2147- ensure_buffer(ps, 5);
2148- if (!after_else && has_token(ps, L"else")) {
2149- ps->index += 4;
2367+ if (!after_else && ps->tokentype == TT_ELSE) {
2368+ next_token(ps);
21502369 after_else = true;
2151- } else if (!after_else && has_token(ps, L"elif")) {
2152- ps->index += 4;
2153- } else if (has_token(ps, L"fi")) {
2154- ps->index += 2;
2370+ } else if (!after_else && ps->tokentype == TT_ELIF) {
2371+ next_token(ps);
2372+ } else if (ps->tokentype == TT_FI) {
2373+ next_token(ps);
21552374 break;
21562375 } else {
21572376 print_errmsg_token_missing(ps, L"fi");
@@ -2161,12 +2380,13 @@
21612380 return result;
21622381 }
21632382
2164-/* Parses a for command. */
2383+/* Parses a for command.
2384+ * The current token must be the starting "for". Never returns NULL. */
21652385 command_T *parse_for(parsestate_T *ps)
21662386 {
2167- assert(has_token(ps, L"for"));
2168- ps->index += 3;
2169- skip_blanks_and_comment(ps);
2387+ assert(ps->tokentype == TT_FOR);
2388+ next_token(ps);
2389+ psubstitute_alias_recursive(ps, 0);
21702390
21712391 command_T *result = xmalloc(sizeof *result);
21722392 result->next = NULL;
@@ -2175,35 +2395,30 @@
21752395 result->c_lineno = ps->info->lineno;
21762396 result->c_redirs = NULL;
21772397
2178- wchar_t *name = parse_word_as_wcs(ps);
2179- if (!(posixly_correct ? is_portable_name : is_name)(name)) {
2180- if (name[0] == L'\0')
2398+ result->c_forname =
2399+ xwcsndup(&ps->src.contents[ps->index], ps->next_index - ps->index);
2400+ if (!is_name_word(ps->token)) {
2401+ if (ps->token == NULL)
21812402 serror(ps, Ngt("an identifier is required after `for'"));
21822403 else
2183- serror(ps, Ngt("`%ls' is not a valid identifier"), name);
2404+ serror(ps, Ngt("`%ls' is not a valid identifier"),
2405+ result->c_forname);
21842406 }
2185- result->c_forname = name;
2407+ next_token(ps);
21862408
21872409 parse_in:;
2188- bool on_next_line = skip_to_next_token(ps);
2189- ensure_buffer(ps, 3);
2190- if (has_token(ps, L"in")) {
2191- redir_T *redirs = NULL;
2192- ps->index += 2;
2193- skip_blanks_and_comment(ps);
2194- result->c_forwords = parse_words_and_redirects(ps, &redirs, false);
2195- if (redirs != NULL) {
2196- serror(ps, Ngt("redirections are not allowed after `in'"));
2197- redirsfree(redirs);
2198- }
2199- if (ps->src.contents[ps->index] == L';')
2200- ps->index++;
2410+ bool on_next_line = parse_newline_list(ps);
2411+ if (ps->tokentype == TT_IN) {
2412+ next_token(ps);
2413+ result->c_forwords = parse_words(ps, false);
2414+ if (ps->tokentype == TT_SEMICOLON)
2415+ next_token(ps);
22012416 } else if (psubstitute_alias(ps, 0)) {
22022417 goto parse_in;
22032418 } else {
22042419 result->c_forwords = NULL;
2205- if (ps->src.contents[ps->index] == L';') {
2206- ps->index++;
2420+ if (ps->tokentype == TT_SEMICOLON) {
2421+ next_token(ps);
22072422 if (on_next_line)
22082423 serror(ps, Ngt("`;' cannot appear on a new line"));
22092424 }
@@ -2210,10 +2425,9 @@
22102425 }
22112426
22122427 parse_do:
2213- skip_to_next_token(ps);
2214- ensure_buffer(ps, 3);
2215- if (has_token(ps, L"do"))
2216- ps->index += 2;
2428+ parse_newline_list(ps);
2429+ if (ps->tokentype == TT_DO)
2430+ next_token(ps);
22172431 else if (psubstitute_alias(ps, 0))
22182432 goto parse_do;
22192433 else
@@ -2225,21 +2439,26 @@
22252439 serror(ps, Ngt("commands are missing between `%ls' and `%ls'"),
22262440 L"do", L"done");
22272441
2228- ensure_buffer(ps, 5);
2229- if (has_token(ps, L"done"))
2230- ps->index += 4;
2442+ if (ps->tokentype == TT_DONE)
2443+ next_token(ps);
22312444 else
22322445 print_errmsg_token_missing(ps, L"done");
2446+
22332447 return result;
22342448 }
22352449
22362450 /* Parses a while/until command.
2237- * `whltype' must be true for the while command and false for the until command.
2238- */
2239-command_T *parse_while(parsestate_T *ps, bool whltype)
2451+ * The current token must be the starting "while" or "until". Never returns
2452+ * NULL. */
2453+command_T *parse_while(parsestate_T *ps)
22402454 {
2241- assert(has_token(ps, whltype ? L"while" : L"until"));
2242- ps->index += 5;
2455+ bool whltype;
2456+ switch (ps->tokentype) {
2457+ case TT_WHILE: whltype = true; break;
2458+ case TT_UNTIL: whltype = false; break;
2459+ default: assert(false);
2460+ }
2461+ next_token(ps);
22432462
22442463 command_T *result = xmalloc(sizeof *result);
22452464 result->next = NULL;
@@ -2248,33 +2467,37 @@
22482467 result->c_lineno = ps->info->lineno;
22492468 result->c_redirs = NULL;
22502469 result->c_whltype = whltype;
2470+
22512471 result->c_whlcond = parse_compound_list(ps);
22522472 if (posixly_correct && result->c_whlcond == NULL)
22532473 serror(ps, Ngt("commands are missing after `%ls'"),
22542474 whltype ? L"while" : L"until");
2255- ensure_buffer(ps, 3);
2256- if (has_token(ps, L"do"))
2257- ps->index += 2;
2475+
2476+ if (ps->tokentype == TT_DO)
2477+ next_token(ps);
22582478 else
22592479 print_errmsg_token_missing(ps, L"do");
2480+
22602481 result->c_whlcmds = parse_compound_list(ps);
22612482 if (posixly_correct && result->c_whlcmds == NULL)
22622483 serror(ps, Ngt("commands are missing between `%ls' and `%ls'"),
22632484 L"do", L"done");
2264- ensure_buffer(ps, 5);
2265- if (has_token(ps, L"done"))
2266- ps->index += 4;
2485+
2486+ if (ps->tokentype == TT_DONE)
2487+ next_token(ps);
22672488 else
22682489 print_errmsg_token_missing(ps, L"done");
2490+
22692491 return result;
22702492 }
22712493
2272-/* Parses a case command. */
2494+/* Parses a case command.
2495+ * The current token must be the starting "case". Never returns NULL. */
22732496 command_T *parse_case(parsestate_T *ps)
22742497 {
2275- assert(has_token(ps, L"case"));
2276- ps->index += 4;
2277- skip_blanks_and_comment(ps);
2498+ assert(ps->tokentype == TT_CASE);
2499+ next_token(ps);
2500+ psubstitute_alias_recursive(ps, 0);
22782501
22792502 command_T *result = xmalloc(sizeof *result);
22802503 result->next = NULL;
@@ -2282,15 +2505,16 @@
22822505 result->c_type = CT_CASE;
22832506 result->c_lineno = ps->info->lineno;
22842507 result->c_redirs = NULL;
2285- result->c_casword = parse_word(ps, true);
2286- if (result->c_casword == NULL)
2508+ result->c_casword = ps->token, ps->token = NULL;
2509+ if (result->c_casword != NULL)
2510+ next_token(ps);
2511+ else
22872512 serror(ps, Ngt("a word is required after `%ls'"), L"case");
22882513
22892514 parse_in:
2290- skip_to_next_token(ps);
2291- ensure_buffer(ps, 3);
2292- if (has_token(ps, L"in")) {
2293- ps->index += 2;
2515+ parse_newline_list(ps);
2516+ if (ps->tokentype == TT_IN) {
2517+ next_token(ps);
22942518 result->c_casitems = parse_case_list(ps);
22952519 } else if (psubstitute_alias(ps, 0)) {
22962520 goto parse_in;
@@ -2300,24 +2524,22 @@
23002524 result->c_casitems = NULL;
23012525 }
23022526
2303- ensure_buffer(ps, 5);
2304- if (has_token(ps, L"esac"))
2305- ps->index += 4;
2527+ if (ps->tokentype == TT_ESAC)
2528+ next_token(ps);
23062529 else
23072530 print_errmsg_token_missing(ps, L"esac");
2531+
23082532 return result;
23092533 }
23102534
2311-/* Parses the body of a case command (the part between "in" and "esac").
2312- * You don't have to call `skip_to_next_token' before calling this function. */
2535+/* Parses the body of a case command (the part between "in" and "esac"). */
23132536 caseitem_T *parse_case_list(parsestate_T *ps)
23142537 {
23152538 caseitem_T *first = NULL, **lastp = &first;
23162539
23172540 do {
2318- skip_to_next_token(ps);
2319- ensure_buffer(ps, 5);
2320- if (has_token(ps, L"esac"))
2541+ parse_newline_list(ps);
2542+ if (ps->tokentype == TT_ESAC)
23212543 break;
23222544 if (psubstitute_alias(ps, 0))
23232545 continue;
@@ -2329,80 +2551,75 @@
23292551 ci->ci_patterns = parse_case_patterns(ps);
23302552 ci->ci_commands = parse_compound_list(ps);
23312553 /* `ci_commands' may be NULL unlike for and while commands */
2332- ensure_buffer(ps, 2);
2333- if (ps->src.contents[ps->index] == L';' &&
2334- ps->src.contents[ps->index + 1] == L';') {
2335- ps->index += 2;
2336- } else {
2554+ if (ps->tokentype == TT_DOUBLE_SEMICOLON)
2555+ next_token(ps);
2556+ else
23372557 break;
2338- }
23392558 } while (!ps->error);
23402559 return first;
23412560 }
23422561
23432562 /* Parses patterns of a case item.
2344- * The current position is advanced to the character that just follows ')', not
2345- * to the next token.
2346- * Call `skip_to_next_token' and `ensure_buffer(ps, 1)' before calling this
2347- * function. */
2563+ * This function consumes the closing ")".
2564+ * Perform alias substitution before calling this function. */
23482565 void **parse_case_patterns(parsestate_T *ps)
23492566 {
23502567 plist_T wordlist;
23512568 pl_init(&wordlist);
23522569
2353- if (ps->src.contents[ps->index] == L'(') { /* ignore the first '(' */
2354- ps->index++;
2355- skip_blanks_and_comment(ps);
2356- if (posixly_correct) {
2357- ensure_buffer(ps, 5);
2358- if (has_token(ps, L"esac"))
2359- serror(ps, Ngt(
2360- "an unquoted `esac' cannot be the first case pattern"));
2361- }
2570+ if (ps->tokentype == TT_LPAREN) { /* ignore the first '(' */
2571+ next_token(ps);
2572+ do {
2573+ if (posixly_correct && ps->tokentype == TT_ESAC)
2574+ serror(ps,
2575+ Ngt("an unquoted `esac' cannot be the first case pattern"));
2576+ } while (psubstitute_alias(ps, 0));
23622577 }
23632578
23642579 const wchar_t *predecessor = L"(";
23652580 do {
2366- if (is_token_delimiter_char(ps->src.contents[ps->index])) {
2367- if (ps->src.contents[ps->index] != L'\0') {
2368- if (ps->src.contents[ps->index] == L'\n')
2369- serror(ps, Ngt("a word is required after `%ls'"),
2370- predecessor);
2371- else
2372- serror(ps, Ngt("encountered an invalid character `%lc' "
2373- "in the case pattern"),
2374- (wint_t) ps->src.contents[ps->index]);
2581+ if (ps->token == NULL) {
2582+ if (ps->tokentype == TT_END_OF_INPUT) {
2583+ // serror(ps, ...);
2584+ } else if (ps->tokentype == TT_NEWLINE) {
2585+ serror(ps, Ngt("a word is required after `%ls'"),
2586+ predecessor);
2587+ } else {
2588+ serror(ps, Ngt("encountered an invalid character `%lc' "
2589+ "in the case pattern"),
2590+ (wint_t) ps->src.contents[ps->index]);
23752591 }
23762592 break;
23772593 }
2378- pl_add(&wordlist, parse_word(ps, true));
2379- skip_blanks_and_comment(ps);
2594+ pl_add(&wordlist, ps->token), ps->token = NULL;
2595+
2596+ next_token(ps);
23802597 psubstitute_alias_recursive(ps, 0);
2381- ensure_buffer(ps, 1);
2382- if (ps->src.contents[ps->index] == L'|') {
2383- predecessor = L"|";
2384- ps->index++;
2385- } else if (ps->src.contents[ps->index] == L')') {
2386- ps->index++;
2598+ if (ps->tokentype != TT_PIPE) {
2599+ if (ps->tokentype == TT_RPAREN)
2600+ next_token(ps);
2601+ else
2602+ serror(ps, Ngt("`%ls' is missing"), L")");
23872603 break;
2388- } else {
2389- serror(ps, Ngt("`%ls' is missing"), L")");
2390- break;
23912604 }
2392- skip_blanks_and_comment(ps);
2605+ predecessor = L"|";
2606+ next_token(ps);
2607+ psubstitute_alias_recursive(ps, 0);
23932608 } while (!ps->error);
2609+
23942610 return pl_toary(&wordlist);
23952611 }
23962612
2397-/* Parses a function definition that starts with the "function" keyword. */
2613+/* Parses a function definition that starts with the "function" keyword.
2614+ * The current token must be "function". Never returns NULL. */
23982615 command_T *parse_function(parsestate_T *ps)
23992616 {
24002617 if (posixly_correct)
24012618 serror(ps, Ngt("`%ls' cannot be used as a command name"), L"function");
24022619
2403- assert(has_token(ps, L"function"));
2404- ps->index += 8;
2405- skip_blanks_and_comment(ps);
2620+ assert(ps->tokentype == TT_FUNCTION);
2621+ next_token(ps);
2622+ psubstitute_alias_recursive(ps, 0);
24062623
24072624 command_T *result = xmalloc(sizeof *result);
24082625 result->next = NULL;
@@ -2410,39 +2627,41 @@
24102627 result->c_type = CT_FUNCDEF;
24112628 result->c_lineno = ps->info->lineno;
24122629 result->c_redirs = NULL;
2413- result->c_funcname = parse_word(ps, true);
2630+ result->c_funcname = ps->token, ps->token = NULL;
24142631 if (result->c_funcname == NULL)
24152632 serror(ps, Ngt("a word is required after `%ls'"), L"function");
2416- skip_blanks_and_comment(ps);
24172633
24182634 bool paren = false;
2419-parse_parentheses:;
2420- size_t saveindex = ps->index;
2421- if (ps->src.contents[ps->index] == L'(') {
2422- ps->index++;
2635+ next_token(ps);
2636+parse_parentheses:
2637+ if (ps->tokentype == TT_LPAREN) {
2638+ size_t saveindex = ps->index;
2639+ next_token(ps);
24232640 parse_close_parenthesis:
2424- skip_blanks_and_comment(ps);
2425- if (ps->src.contents[ps->index] == L')')
2426- paren = true, ps->index++;
2427- else if (psubstitute_alias(ps, AF_NONGLOBAL))
2641+ if (ps->tokentype == TT_RPAREN) {
2642+ paren = true;
2643+ next_token(ps);
2644+ } else if (psubstitute_alias(ps, AF_NONGLOBAL)) {
24282645 goto parse_close_parenthesis;
2429- else
2646+ } else {
2647+ /* rewind to '(' */
24302648 rewind_index(ps, saveindex);
2649+ ps->next_index = ps->index;
2650+ next_token(ps);
2651+ }
24312652 }
2432- skip_to_next_token(ps);
2653+parse_function_body:
2654+ parse_newline_list(ps);
24332655
2434-parse_function_body:;
2435- const wchar_t *t = check_opening_token(ps);
2436- if (t != NULL) {
2437- result->c_funcbody = parse_compound_command(ps, t);
2438- } else if (psubstitute_alias(ps, 0)) {
2439- if (paren)
2440- goto parse_function_body;
2441- else
2442- goto parse_parentheses;
2443- } else {
2656+ result->c_funcbody = parse_compound_command(ps);
2657+ if (result->c_funcbody == NULL) {
2658+ if (psubstitute_alias(ps, 0)) {
2659+ if (paren)
2660+ goto parse_function_body;
2661+ else
2662+ goto parse_parentheses;
2663+ }
24442664 serror(ps, Ngt("a function body must be a compound command"));
2445- result->c_funcbody = NULL;
24462665 }
24472666
24482667 return result;
@@ -2456,8 +2675,7 @@
24562675 * If successful, `c' is directly modified to the function definition parsed. */
24572676 command_T *try_reparse_as_function(parsestate_T *ps, command_T *c)
24582677 {
2459- // ensure_buffer(ps, 1);
2460- if (ps->src.contents[ps->index] != L'(') // not a function definition?
2678+ if (ps->tokentype != TT_LPAREN) // not a function definition?
24612679 return c;
24622680
24632681 /* If this is a function definition, there must be exactly one command word
@@ -2471,41 +2689,40 @@
24712689
24722690 /* The name must be valid. */
24732691 wordunit_T *name = c->c_words[0];
2474- if (!is_literal_function_name(name)) {
2692+ if (!is_name_word(name)) {
24752693 serror(ps, Ngt("invalid function name"));
24762694 return c;
24772695 }
24782696
24792697 /* Skip '('. */
2480- ps->index++;
2481- skip_blanks_and_comment(ps);
2698+ next_token(ps);
24822699
24832700 /* Parse ')'. */
24842701 psubstitute_alias_recursive(ps, 0);
2485- // ensure_buffer(ps, 1);
2486- if (ps->src.contents[ps->index] != L')') {
2702+ if (ps->tokentype != TT_RPAREN) {
24872703 serror(ps, Ngt("`(' must be followed by `)' in a function definition"));
24882704 return c;
24892705 }
2490- ps->index++;
2706+ next_token(ps);
2707+
2708+ free(c->c_words);
2709+ c->c_type = CT_FUNCDEF;
2710+ c->c_funcname = name;
2711+
24912712 parse_function_body:
2492- skip_to_next_token(ps);
2493-
2494- const wchar_t *t = check_opening_token(ps);
2495- if (t == NULL) {
2713+ parse_newline_list(ps);
2714+ c->c_funcbody = parse_compound_command(ps);
2715+ if (c->c_funcbody == NULL) {
24962716 if (psubstitute_alias(ps, 0))
24972717 goto parse_function_body;
24982718 serror(ps, Ngt("a function body must be a compound command"));
2499- return c;
25002719 }
2501- free(c->c_words);
25022720
2503- c->c_type = CT_FUNCDEF;
2504- c->c_funcname = name;
2505- c->c_funcbody = parse_compound_command(ps, t);
25062721 return c;
25072722 }
25082723
2724+/***** Here-document contents *****/
2725+
25092726 /* Reads the contents of a here-document. */
25102727 void read_heredoc_contents(parsestate_T *ps, redir_T *r)
25112728 {
@@ -2645,7 +2862,7 @@
26452862 size_t startindex = ps->index;
26462863
26472864 for (;;) {
2648- ensure_buffer(ps, 1);
2865+ maybe_line_continuations(ps, ps->index);
26492866 switch (ps->src.contents[ps->index]) {
26502867 case L'\0':
26512868 goto done;
@@ -2689,109 +2906,7 @@
26892906 return lastp;
26902907 }
26912908
2692-/* Parses a string recognizing parameter expansions, command substitutions of
2693- * the form "$(...)" and arithmetic expansions.
2694- * All the members of `info' except `lastinputresult' must have been initialized
2695- * beforehand.
2696- * This function reads and parses the input to the end of file.
2697- * Iff successful, the result is assigned to `*resultp' and true is returned.
2698- * If the input is empty, NULL is assigned.
2699- * On error, the value of `*resultp' is undefined. */
2700-bool parse_string(parseparam_T *info, wordunit_T **restrict resultp)
2701-{
2702- parsestate_T ps = {
2703- .info = info,
2704- .error = false,
2705- .index = 0,
2706- .enable_alias = false,
2707- .reparse = false,
2708- .aliases = NULL,
2709- };
2710- wb_init(&ps.src);
27112909
2712- ps.info->lastinputresult = INPUT_OK;
2713- read_more_input(&ps);
2714- pl_init(&ps.pending_heredocs);
2715-
2716- resultp = parse_string_without_quotes(&ps, false, false, resultp);
2717- *resultp = NULL;
2718-
2719- wb_destroy(&ps.src);
2720- pl_destroy(&ps.pending_heredocs);
2721- assert(ps.aliases == NULL);
2722- //destroy_aliaslist(ps.aliases);
2723- if (ps.info->lastinputresult != INPUT_EOF || ps.error) {
2724- wordfree(*resultp);
2725- return false;
2726- } else {
2727- return true;
2728- }
2729-}
2730-
2731-
2732-/***** Auxiliaries about Error Messages *****/
2733-
2734-const char *get_errmsg_unexpected_token(const wchar_t *t)
2735-{
2736- switch (t[0]) {
2737- case L')':
2738- assert(wcscmp(t, L")") == 0);
2739- return Ngt("encountered `%ls' without a matching `('");
2740- case L'}':
2741- assert(wcscmp(t, L"}") == 0);
2742- return Ngt("encountered `%ls' without a matching `{'");
2743- case L';':
2744- assert(wcscmp(t, L";;") == 0);
2745- return Ngt("`%ls' is used outside `case'");
2746- case L'!':
2747- assert(wcscmp(t, L"!") == 0);
2748- return Ngt("`%ls' cannot be used as a command name");
2749- case L'i':
2750- assert(wcscmp(t, L"in") == 0);
2751- return Ngt("`%ls' cannot be used as a command name");
2752- case L'f':
2753- assert(wcscmp(t, L"fi") == 0);
2754- return Ngt("encountered `%ls' "
2755- "without a matching `if' and/or `then'");
2756- case L't':
2757- assert(wcscmp(t, L"then") == 0);
2758- return Ngt("encountered `%ls' without a matching `if' or `elif'");
2759- case L'd':
2760- assert(t[1] == L'o');
2761- if (t[2] == L'\0') {
2762- assert(wcscmp(t, L"do") == 0);
2763- return Ngt("encountered `%ls' "
2764- "without a matching `for', `while', or `until'");
2765- } else {
2766- assert(wcscmp(t, L"done") == 0);
2767- return Ngt("encountered `%ls' without a matching `do'");
2768- }
2769- case L'e':
2770- if (t[1] == L's') {
2771- assert(wcscmp(t, L"esac") == 0);
2772- return Ngt("encountered `%ls' without a matching `case'");
2773- } else {
2774- assert(wcscmp(t, L"else") == 0 || wcscmp(t, L"elif") == 0);
2775- return Ngt("encountered `%ls' "
2776- "without a matching `if' and/or `then'");
2777- }
2778- default:
2779- assert(false);
2780- }
2781-}
2782-
2783-void print_errmsg_token_missing(parsestate_T *ps, const wchar_t *t)
2784-{
2785- const wchar_t *atoken = check_closing_token(ps);
2786- if (atoken != NULL) {
2787- serror(ps, get_errmsg_unexpected_token(atoken), atoken);
2788- serror(ps, Ngt("(maybe you missed `%ls'?)"), t);
2789- } else {
2790- serror(ps, Ngt("`%ls' is missing"), t);
2791- }
2792-}
2793-
2794-
27952910 /********** Functions that Convert Parse Trees into Strings **********/
27962911
27972912 struct print {
@@ -3167,7 +3282,7 @@
31673282 {
31683283 assert(c->c_type == CT_FUNCDEF);
31693284
3170- if (!is_literal_function_name(c->c_funcname))
3285+ if (!is_name_word(c->c_funcname))
31713286 wb_cat(&pr->buffer, L"function ");
31723287 print_word(pr, c->c_funcname, indent);
31733288 wb_cat(&pr->buffer, L"()");
--- yash/trunk/po/ja.po (revision 3888)
+++ yash/trunk/po/ja.po (revision 3889)
@@ -1,14 +1,14 @@
11 # Japanese translations for yash package
22 # yash パッケージのメッセージの和訳
3-# Copyright (C) 2010-2017 Magicant
3+# Copyright (C) 2010-2018 Magicant
44 # This file is distributed under the same license as the yash package.
55 # WATANABE Yuki <magicant@users.osdn.me>, 2010-2017.
66 #
77 msgid ""
88 msgstr ""
9-"Project-Id-Version: yash 2.46\n"
9+"Project-Id-Version: yash 2.48\n"
1010 "Report-Msgid-Bugs-To: http://osdn.jp/projects/yash/forums/\n"
11-"POT-Creation-Date: 2017-10-28 12:56+0900\n"
11+"POT-Creation-Date: 2018-09-23 11:28+0900\n"
1212 "PO-Revision-Date: 2017-10-28 13:09+0900\n"
1313 "Last-Translator: WATANABE Yuki <magicant@users.osdn.me>\n"
1414 "Language-Team: Japanese\n"
@@ -18,34 +18,34 @@
1818 "Content-Transfer-Encoding: 8bit\n"
1919 "Plural-Forms: nplurals=1; plural=0;\n"
2020
21-#: alias.c:401
21+#: alias.c:403
2222 #, c-format
2323 msgid "%ls: an alias for `%ls'\n"
2424 msgstr "%ls: 「%ls」へのエイリアス\n"
2525
26-#: alias.c:497 alias.c:548
26+#: alias.c:499 alias.c:550
2727 #, c-format
2828 msgid "no such alias `%ls'"
2929 msgstr "「%ls」というエイリアスはありません"
3030
31-#: alias.c:500
31+#: alias.c:502
3232 #, c-format
3333 msgid "`%ls' is not a valid alias name"
3434 msgstr "「%ls」は有効なエイリアス名ではありません"
3535
36-#: alias.c:509
36+#: alias.c:511
3737 msgid "define or print aliases"
3838 msgstr "エイリアスを定義または表示する"
3939
40-#: alias.c:512
40+#: alias.c:514
4141 msgid "\talias [-gp] [name[=value]...]\n"
4242 msgstr "\talias [-gp] [名前[=値]...]\n"
4343
44-#: alias.c:556
44+#: alias.c:558
4545 msgid "undefine aliases"
4646 msgstr "エイリアスの定義を削除する"
4747
48-#: alias.c:559
48+#: alias.c:561
4949 msgid ""
5050 "\tunalias name...\n"
5151 "\tunalias -a\n"
@@ -218,8 +218,8 @@
218218 msgstr "「%ls」は有効な数値ではありません"
219219
220220 #: builtins/printf.c:693 builtins/test.c:556 builtins/test.c:562
221-#: builtins/ulimit.c:229 exec.c:1694 exec.c:1767 history.c:1714 sig.c:1395
222-#: variable.c:2088 variable.c:2148 variable.c:2187 variable.c:2371 yash.c:633
221+#: builtins/ulimit.c:229 exec.c:1725 exec.c:1798 history.c:1714 sig.c:1395
222+#: variable.c:2102 variable.c:2162 variable.c:2201 variable.c:2385 yash.c:633
223223 #, c-format
224224 msgid "`%ls' is not a valid integer"
225225 msgstr "「%ls」は有効な整数ではありません"
@@ -232,9 +232,9 @@
232232 msgid "\tprintf format [value...]\n"
233233 msgstr "\tprintf 書式 [値...]\n"
234234
235-#: builtins/test.c:82 builtins/test.c:425 parser.c:1257 parser.c:1691
236-#: parser.c:1776 parser.c:1804 parser.c:2198 parser.c:2276 parser.c:2367
237-#: parser.c:2768
235+#: builtins/test.c:82 builtins/test.c:425 parser.c:850 parser.c:1348
236+#: parser.c:1436 parser.c:1464 parser.c:1591 parser.c:1630 parser.c:2130
237+#: parser.c:2434 parser.c:2522 parser.c:2602
238238 #, c-format
239239 msgid "`%ls' is missing"
240240 msgstr "「%ls」が抜けています"
@@ -249,8 +249,8 @@
249249 msgid "`%ls' is not a unary operator"
250250 msgstr "「%ls」は単項演算子ではありません"
251251
252-#: builtins/test.c:159 builtins/test.c:627 builtins/test.c:635 exec.c:1903
253-#: exec.c:2041 exec.c:2050 history.c:1492 lineedit/keymap.c:476 path.c:1106
252+#: builtins/test.c:159 builtins/test.c:627 builtins/test.c:635 exec.c:1934
253+#: exec.c:2078 exec.c:2087 history.c:1492 lineedit/keymap.c:476 path.c:1106
254254 #: path.c:1173 strbuf.c:573 strbuf.c:596
255255 msgid "unexpected error"
256256 msgstr "想定外のエラーです"
@@ -371,72 +371,72 @@
371371 "\tulimit -a [-H|-S]\n"
372372 "\tulimit [-H|-S] [-efilnqrstuvx] [制限]\n"
373373
374-#: exec.c:747
374+#: exec.c:771
375375 msgid "cannot open a pipe"
376376 msgstr "パイプを開けません"
377377
378-#: exec.c:975
378+#: exec.c:998
379379 msgid "cannot make a child process"
380380 msgstr "子プロセスを生成できません"
381381
382-#: exec.c:1184 exec.c:2065 exec.c:2292
382+#: exec.c:1208 exec.c:2102 exec.c:2329
383383 #, c-format
384384 msgid "no such command `%s'"
385385 msgstr "「%s」というようなコマンドはありません"
386386
387-#: exec.c:1238
387+#: exec.c:1262
388388 #, c-format
389389 msgid "cannot execute command `%s'"
390390 msgstr "コマンド「%s」を実行できません"
391391
392-#: exec.c:1239
392+#: exec.c:1263
393393 #, c-format
394394 msgid "cannot execute command `%s' (%s)"
395395 msgstr "コマンド「%s」(%s) を実行できません"
396396
397-#: exec.c:1339
397+#: exec.c:1366
398398 #, c-format
399399 msgid "cannot invoke a new shell to execute script `%s'"
400400 msgstr "スクリプト「%s」を実行するための新しいシェルを起動できません"
401401
402-#: exec.c:1394 exec.c:1416
402+#: exec.c:1425 exec.c:1447
403403 msgid "cannot open a pipe for the command substitution"
404404 msgstr "コマンド置換のためのパイプを開けません"
405405
406-#: exec.c:1453
406+#: exec.c:1484
407407 msgid "command substitution"
408408 msgstr "コマンド置換"
409409
410-#: exec.c:1704
410+#: exec.c:1735
411411 msgid "cannot be used in the interactive mode"
412412 msgstr "対話モードでは使えません"
413413
414-#: exec.c:1714
414+#: exec.c:1745
415415 msgid "return from a function or script"
416416 msgstr "関数やスクリプトから抜ける"
417417
418-#: exec.c:1717
418+#: exec.c:1748
419419 msgid "\treturn [-n] [exit_status]\n"
420420 msgstr "\treturn [-n] [終了ステータス]\n"
421421
422-#: exec.c:1749
422+#: exec.c:1780
423423 msgid "not in an iteration"
424424 msgstr "反復実行の途中ではありません"
425425
426-#: exec.c:1770
426+#: exec.c:1801
427427 #, c-format
428428 msgid "%u is not a positive integer"
429429 msgstr "%u は正の整数ではありません"
430430
431-#: exec.c:1780
431+#: exec.c:1811
432432 msgid "not in a loop"
433433 msgstr "ループの途中ではありません"
434434
435-#: exec.c:1799
435+#: exec.c:1830
436436 msgid "exit a loop"
437437 msgstr "ループを抜ける"
438438
439-#: exec.c:1802
439+#: exec.c:1833
440440 msgid ""
441441 "\tbreak [count]\n"
442442 "\tbreak -i\n"
@@ -444,11 +444,11 @@
444444 "\tbreak [深さ]\n"
445445 "\tbreak -i\n"
446446
447-#: exec.c:1807
447+#: exec.c:1838
448448 msgid "continue a loop"
449449 msgstr "ループの先頭に戻る"
450450
451-#: exec.c:1810
451+#: exec.c:1841
452452 msgid ""
453453 "\tcontinue [count]\n"
454454 "\tcontinue -i\n"
@@ -456,104 +456,104 @@
456456 "\tcontinue [深さ]\n"
457457 "\tcontinue -i\n"
458458
459-#: exec.c:1850
459+#: exec.c:1881
460460 msgid "evaluate arguments as a command"
461461 msgstr "引数をコマンドとして実行する"
462462
463-#: exec.c:1853
463+#: exec.c:1884
464464 msgid "\teval [-i] [argument...]\n"
465465 msgstr "\teval [-i] [引数...]\n"
466466
467-#: exec.c:1912
467+#: exec.c:1943
468468 #, c-format
469469 msgid "file `%s' was not found in $YASH_LOADPATH"
470470 msgstr "ファイル「%s」は $YASH_LOADPATH 内に見つかりませんでした"
471471
472-#: exec.c:1922
472+#: exec.c:1953
473473 #, c-format
474474 msgid "file `%s' was not found in $PATH"
475475 msgstr "ファイル「%s」は $PATH 内に見つかりませんでした"
476476
477-#: exec.c:1939 redir.c:279 yash.c:206
477+#: exec.c:1965 redir.c:279 yash.c:206
478478 #, c-format
479479 msgid "cannot open file `%s'"
480480 msgstr "ファイル「%s」を開けません"
481481
482-#: exec.c:1967
482+#: exec.c:2004
483483 msgid "read a file and execute commands"
484484 msgstr "ファイルをスクリプトとして実行する"
485485
486-#: exec.c:1970
486+#: exec.c:2007
487487 msgid "\t. [-AL] file [argument...]\n"
488488 msgstr "\t. [-AL] ファイル [引数...]\n"
489489
490-#: exec.c:2018 yash.c:620
490+#: exec.c:2055 yash.c:620
491491 #, c-format
492492 msgid "You have a stopped job!"
493493 msgid_plural "You have %zu stopped jobs!"
494494 msgstr[0] "停止中のジョブが %zu 個あります!"
495495
496-#: exec.c:2022
496+#: exec.c:2059
497497 msgid " Use the -f option to exec anyway.\n"
498498 msgstr " それでも exec するには -f オプションを付けてください。\n"
499499
500-#: exec.c:2112
500+#: exec.c:2149
501501 msgid "replace the shell process with an external command"
502502 msgstr "シェルのプロセスを外部コマンドに変える"
503503
504-#: exec.c:2115
504+#: exec.c:2152
505505 msgid "\texec [-cf] [-a name] [command [argument...]]\n"
506506 msgstr "\texec [-cf] [-a 名前] [コマンド [引数...]]\n"
507507
508-#: exec.c:2175
508+#: exec.c:2212
509509 msgid "the -a or -k option must be used with the -v option"
510510 msgstr "-a および -k オプションは -v オプションと一緒にしか使えません"
511511
512-#: exec.c:2269
512+#: exec.c:2306
513513 #, c-format
514514 msgid "%ls: a shell keyword\n"
515515 msgstr "%ls: シェルの予約語\n"
516516
517-#: exec.c:2298
517+#: exec.c:2335
518518 #, c-format
519519 msgid "%s: a special built-in\n"
520520 msgstr "%s: 特殊組込みコマンド\n"
521521
522-#: exec.c:2302
522+#: exec.c:2339
523523 #, c-format
524524 msgid "%s: a semi-special built-in\n"
525525 msgstr "%s: 準特殊組込みコマンド\n"
526526
527-#: exec.c:2314
527+#: exec.c:2351
528528 #, c-format
529529 msgid "%s: a regular built-in (not found in $PATH)\n"
530530 msgstr "%s: 通常の組込みコマンド ($PATH 内に存在せず)\n"
531531
532-#: exec.c:2315
532+#: exec.c:2352
533533 #, c-format
534534 msgid "%s: a regular built-in at %s\n"
535535 msgstr "%s: 通常の組込みコマンド (%s)\n"
536536
537-#: exec.c:2322
537+#: exec.c:2359
538538 #, c-format
539539 msgid "%s: a function\n"
540540 msgstr "%s: 関数\n"
541541
542-#: exec.c:2338
542+#: exec.c:2375
543543 #, c-format
544544 msgid "%s: an external command at %s\n"
545545 msgstr "%s: 外部コマンド (%s)\n"
546546
547-#: exec.c:2360
547+#: exec.c:2397
548548 #, c-format
549549 msgid "%s: an external command at %s/%s\n"
550550 msgstr "%s: 外部コマンド (%s/%s)\n"
551551
552-#: exec.c:2369
552+#: exec.c:2406
553553 msgid "execute or identify a command"
554554 msgstr "コマンドを実行または特定する"
555555
556-#: exec.c:2372
556+#: exec.c:2409
557557 msgid ""
558558 "\tcommand [-befp] command [argument...]\n"
559559 "\tcommand -v|-V [-abefkp] command...\n"
@@ -561,23 +561,23 @@
561561 "\tcommand [-befp] コマンド [引数...]\n"
562562 "\tcommand -v|-V [-abefkp] コマンド...\n"
563563
564-#: exec.c:2377
564+#: exec.c:2414
565565 msgid "identify a command"
566566 msgstr "コマンドを特定する"
567567
568-#: exec.c:2380
568+#: exec.c:2417
569569 msgid "\ttype command...\n"
570570 msgstr "\ttype コマンド...\n"
571571
572-#: exec.c:2418
572+#: exec.c:2455
573573 msgid "cannot get the time data"
574574 msgstr "時間情報を取得できません"
575575
576-#: exec.c:2435
576+#: exec.c:2472
577577 msgid "print CPU time usage"
578578 msgstr "消費 CPU 時間を表示する"
579579
580-#: exec.c:2438
580+#: exec.c:2475
581581 msgid "\ttimes\n"
582582 msgstr "\ttimes\n"
583583
@@ -660,7 +660,7 @@
660660 msgid "cannot invoke the editor to edit history"
661661 msgstr "履歴を編集するためのエディタを起動できません"
662662
663-#: history.c:1545 history.c:1571 lineedit/editing.c:2905 redir.c:796
663+#: history.c:1545 history.c:1571 lineedit/editing.c:2905 redir.c:826
664664 #, c-format
665665 msgid "failed to remove temporary file `%s'"
666666 msgstr "一時ファイル「%s」を削除できませんでした"
@@ -707,11 +707,11 @@
707707 msgstr ""
708708 "\thistory [-cF] [-d 項目] [-s コマンド] [-r ファイル] [-w ファイル] [個数]\n"
709709
710-#: input.c:166 lineedit/lineedit.c:221
710+#: input.c:170 lineedit/lineedit.c:221
711711 msgid "cannot read input"
712712 msgstr "入力を読み込めません"
713713
714-#: input.c:265 input.c:283 input.c:292
714+#: input.c:288
715715 msgid "prompt"
716716 msgstr "プロンプト"
717717
@@ -935,49 +935,49 @@
935935 msgid "You have new mail."
936936 msgstr "新着メールがあります。"
937937
938-#: option.c:413 xgetopt.c:414
938+#: option.c:417 xgetopt.c:414
939939 #, c-format
940940 msgid "the -%lc option requires an argument"
941941 msgstr "-%lc オプションの引数が抜けています"
942942
943-#: option.c:460 option.c:579 sig.c:1339 xgetopt.c:382
943+#: option.c:464 option.c:583 sig.c:1339 xgetopt.c:382
944944 #, c-format
945945 msgid "`%ls' is not a valid option"
946946 msgstr "「%ls」は有効なオプションではありません"
947947
948-#: option.c:602 xgetopt.c:417
948+#: option.c:606 xgetopt.c:417
949949 #, c-format
950950 msgid "the --%ls option requires an argument"
951951 msgstr "--%ls オプションは引数を取りません"
952952
953-#: option.c:615 xgetopt.c:431
953+#: option.c:619 xgetopt.c:431
954954 #, c-format
955955 msgid "%ls: the --%ls option does not take an argument"
956956 msgstr "%ls: --%ls オプションは引数を取りません"
957957
958-#: option.c:624 xgetopt.c:392
958+#: option.c:628 xgetopt.c:392
959959 #, c-format
960960 msgid "option `%ls' is ambiguous"
961961 msgstr "オプション「%ls」は曖昧です"
962962
963-#: option.c:648
963+#: option.c:652
964964 #, c-format
965965 msgid "the %ls option cannot be changed once the shell has been initialized"
966966 msgstr "シェルが初期化された後は %ls オプションを変更することはできません"
967967
968-#: option.c:914
968+#: option.c:918
969969 msgid "on"
970970 msgstr "入"
971971
972-#: option.c:915
972+#: option.c:919
973973 msgid "off"
974974 msgstr "切"
975975
976-#: option.c:939
976+#: option.c:943
977977 msgid "set shell options and positional parameters"
978978 msgstr "シェルオプションと位置パラメータを設定する"
979979
980-#: option.c:942
980+#: option.c:946
981981 msgid ""
982982 "\tset [option...] [--] [new_positional_parameter...]\n"
983983 "\tset -o|+o # print current settings\n"
@@ -985,196 +985,204 @@
985985 "\tset [オプション...] [--] [新しい位置パラメータ...]\n"
986986 "\tset -o|+o # 現在の設定を表示する\n"
987987
988-#: parser.c:620
988+#: parser.c:788
989989 msgid "syntax error: "
990990 msgstr "構文エラー: "
991991
992-#: parser.c:918
993-msgid "`;' or `&' is missing"
994-msgstr "「;」または「&」が抜けています"
992+#: parser.c:802
993+#, c-format
994+msgid "encountered `%ls' without a matching `('"
995+msgstr "「%ls」に対応する「(」がありません"
995996
996-#: parser.c:1035
997-msgid "ksh-like extended glob pattern `!(...)' is not supported"
998-msgstr "ksh の拡張パターン「!(...)」は利用できません"
997+#: parser.c:804
998+#, c-format
999+msgid "encountered `%ls' without a matching `{'"
1000+msgstr "「%ls」に対応する「{」がありません"
9991001
1000-#: parser.c:1117
1001-msgid "a command is missing at the end of input"
1002-msgstr "入力の最後でコマンドが抜けています"
1002+#: parser.c:806
1003+#, c-format
1004+msgid "`%ls' is used outside `case'"
1005+msgstr "「%ls」が case コマンドの外で使われています"
10031006
1004-#: parser.c:1119
1007+#: parser.c:808 parser.c:810 parser.c:2618
10051008 #, c-format
1006-msgid "a command is missing before `%lc'"
1007-msgstr "「%lc」の前にコマンドがありません"
1009+msgid "`%ls' cannot be used as a command name"
1010+msgstr "「%ls」はコマンド名として使えません"
10081011
1009-#: parser.c:1381
1010-msgid "the redirection target is missing"
1011-msgstr "リダイレクトの対象が抜けています"
1012+#: parser.c:812 parser.c:825
1013+#, c-format
1014+msgid "encountered `%ls' without a matching `if' and/or `then'"
1015+msgstr "「%ls」に対応する「if」または「then」がありません"
10121016
1013-#: parser.c:1388
1014-msgid "the end-of-here-document indicator is missing"
1015-msgstr "ヒアドキュメントの終端子が抜けています"
1017+#: parser.c:815
1018+#, c-format
1019+msgid "encountered `%ls' without a matching `if' or `elif'"
1020+msgstr "「%ls」に対応する「if」または「elif」がありません"
10161021
1017-#: parser.c:1407
1018-msgid "unclosed process redirection"
1019-msgstr "プロセスリダイレクトが閉じられていません"
1022+#: parser.c:817
1023+#, c-format
1024+msgid "encountered `%ls' without a matching `for', `while', or `until'"
1025+msgstr "「%ls」に対応する「for」「while」または「until」がありません"
10201026
1021-#: parser.c:1499
1027+#: parser.c:820
1028+#, c-format
1029+msgid "encountered `%ls' without a matching `do'"
1030+msgstr "「%ls」に対応する「do」がありません"
1031+
1032+#: parser.c:822
1033+#, c-format
1034+msgid "encountered `%ls' without a matching `case'"
1035+msgstr "「%ls」に対応する「case」がありません"
1036+
1037+#: parser.c:848
1038+#, c-format
1039+msgid "(maybe you missed `%ls'?)"
1040+msgstr "(「%ls」を忘れていませんか?)"
1041+
1042+#: parser.c:1151
10221043 msgid "the double quotation is not closed"
10231044 msgstr "二重引用符が閉じられていません"
10241045
1025-#: parser.c:1516
1046+#: parser.c:1168
10261047 msgid "the single quotation is not closed"
10271048 msgstr "単一引用符が閉じられていません"
10281049
1029-#: parser.c:1669
1050+#: parser.c:1326
10301051 msgid "the parameter name is missing or invalid"
10311052 msgstr "パラメータ名が抜けているか不正です"
10321053
1033-#: parser.c:1681 parser.c:1686
1054+#: parser.c:1338 parser.c:1343
10341055 msgid "the index is missing"
10351056 msgstr "インデックスが抜けています"
10361057
1037-#: parser.c:1711 parser.c:1724
1058+#: parser.c:1370 parser.c:1383
10381059 #, c-format
10391060 msgid "invalid character `%lc' in parameter expansion"
10401061 msgstr "パラメータ展開に無効な文字「%lc」が混じっています"
10411062
1042-#: parser.c:1720 parser.c:1734 parser.c:1778
1063+#: parser.c:1379 parser.c:1394 parser.c:1438
10431064 #, c-format
10441065 msgid "invalid use of `%lc' in parameter expansion"
10451066 msgstr "パラメータ展開において「%lc」の使い方が正しくありません"
10461067
1047-#: parser.c:1878
1068+#: parser.c:1539
10481069 msgid "the backquoted command substitution is not closed"
10491070 msgstr "「`」によるコマンド置換が閉じられていません"
10501071
1051-#: parser.c:2070 parser.c:2103 parser.c:2203 parser.c:2240
1072+#: parser.c:1786
1073+msgid "`;' or `&' is missing"
1074+msgstr "「;」または「&」が抜けています"
1075+
1076+#: parser.c:1893
1077+msgid "ksh-like extended glob pattern `!(...)' is not supported"
1078+msgstr "ksh の拡張パターン「!(...)」は利用できません"
1079+
1080+#: parser.c:1987
1081+msgid "a command is missing at the end of input"
1082+msgstr "入力の最後でコマンドが抜けています"
1083+
1084+#: parser.c:1989
10521085 #, c-format
1086+msgid "a command is missing before `%lc'"
1087+msgstr "「%lc」の前にコマンドがありません"
1088+
1089+#: parser.c:2191
1090+msgid "pipe redirection is not supported in the POSIXly-correct mode"
1091+msgstr "パイプリダイレクトは POSIX 準拠モードでは使えません"
1092+
1093+#: parser.c:2209
1094+msgid "here-string is not supported in the POSIXly-correct mode"
1095+msgstr "ヒアストリングは POSIX 準拠モードでは使えません"
1096+
1097+#: parser.c:2224
1098+msgid "the redirection target is missing"
1099+msgstr "リダイレクトの対象が抜けています"
1100+
1101+#: parser.c:2234
1102+msgid "the end-of-here-document indicator is missing"
1103+msgstr "ヒアドキュメントの終端子が抜けています"
1104+
1105+#: parser.c:2243
1106+msgid "process redirection is not supported in the POSIXly-correct mode"
1107+msgstr "プロセスリダイレクトは POSIX 準拠モードでは使えません"
1108+
1109+#: parser.c:2249
1110+msgid "unclosed process redirection"
1111+msgstr "プロセスリダイレクトが閉じられていません"
1112+
1113+#: parser.c:2319 parser.c:2353 parser.c:2439 parser.c:2483
1114+#, c-format
10531115 msgid "commands are missing between `%ls' and `%ls'"
10541116 msgstr "「%ls」と「%ls」の間にコマンドがありません"
10551117
1056-#: parser.c:2116 parser.c:2231
1118+#: parser.c:2365 parser.c:2473
10571119 #, c-format
10581120 msgid "commands are missing after `%ls'"
10591121 msgstr "「%ls」の後にコマンドがありません"
10601122
1061-#: parser.c:2159
1123+#: parser.c:2402
10621124 msgid "an identifier is required after `for'"
10631125 msgstr "「for」の後には識別子が必要です"
10641126
1065-#: parser.c:2161
1127+#: parser.c:2404
10661128 #, c-format
10671129 msgid "`%ls' is not a valid identifier"
10681130 msgstr "「%ls」は有効な識別子ではありません"
10691131
1070-#: parser.c:2174
1071-msgid "redirections are not allowed after `in'"
1072-msgstr "「in」の後にリダイレクトは書けません"
1073-
1074-#: parser.c:2186
1132+#: parser.c:2423
10751133 msgid "`;' cannot appear on a new line"
10761134 msgstr "「;」は行頭に置けません"
10771135
1078-#: parser.c:2265 parser.c:2347 parser.c:2393
1136+#: parser.c:2512 parser.c:2585 parser.c:2632
10791137 #, c-format
10801138 msgid "a word is required after `%ls'"
10811139 msgstr "「%ls」の後には単語が必要です"
10821140
1083-#: parser.c:2338
1141+#: parser.c:2575
10841142 msgid "an unquoted `esac' cannot be the first case pattern"
10851143 msgstr "クォートしていない「esac」を最初のパターンにすることはできません"
10861144
1087-#: parser.c:2350
1145+#: parser.c:2588
10881146 #, c-format
10891147 msgid "encountered an invalid character `%lc' in the case pattern"
10901148 msgstr "case のパターン内に不正な文字「%lc」があります"
10911149
1092-#: parser.c:2379 parser.c:2726 parser.c:2729
1093-#, c-format
1094-msgid "`%ls' cannot be used as a command name"
1095-msgstr "「%ls」はコマンド名として使えません"
1096-
1097-#: parser.c:2422 parser.c:2476
1150+#: parser.c:2664 parser.c:2718
10981151 msgid "a function body must be a compound command"
10991152 msgstr "関数の本体は複合コマンドでなければなりません"
11001153
1101-#: parser.c:2446
1154+#: parser.c:2686
11021155 #, c-format
11031156 msgid "invalid use of `%lc'"
11041157 msgstr "「%lc」の使い方が正しくありません"
11051158
1106-#: parser.c:2453
1159+#: parser.c:2693
11071160 msgid "invalid function name"
11081161 msgstr "無効な関数名です"
11091162
1110-#: parser.c:2465
1163+#: parser.c:2703
11111164 msgid "`(' must be followed by `)' in a function definition"
11121165 msgstr "関数定義では「(」の直後に「)」が必要です"
11131166
1114-#: parser.c:2492
1167+#: parser.c:2731
11151168 msgid "the end-of-here-document indicator contains a newline"
11161169 msgstr "ヒアドキュメントの終端子に改行が入っています"
11171170
1118-#: parser.c:2519 parser.c:2553
1171+#: parser.c:2758 parser.c:2792
11191172 #, c-format
11201173 msgid "the here-document content is not closed by `%ls'"
11211174 msgstr "ヒアドキュメントが「%ls」で閉じられていません"
11221175
1123-#: parser.c:2605
1176+#: parser.c:2844
11241177 #, c-format
11251178 msgid "here-document content for %s%ls is missing"
11261179 msgstr "%s%ls に対応するヒアドキュメントの内容がありません"
11271180
1128-#: parser.c:2717
1129-#, c-format
1130-msgid "encountered `%ls' without a matching `('"
1131-msgstr "「%ls」に対応する「(」がありません"
1132-
1133-#: parser.c:2720
1134-#, c-format
1135-msgid "encountered `%ls' without a matching `{'"
1136-msgstr "「%ls」に対応する「{」がありません"
1137-
1138-#: parser.c:2723
1139-#, c-format
1140-msgid "`%ls' is used outside `case'"
1141-msgstr "「%ls」が case コマンドの外で使われています"
1142-
1143-#: parser.c:2732 parser.c:2753
1144-#, c-format
1145-msgid "encountered `%ls' without a matching `if' and/or `then'"
1146-msgstr "「%ls」に対応する「if」または「then」がありません"
1147-
1148-#: parser.c:2736
1149-#, c-format
1150-msgid "encountered `%ls' without a matching `if' or `elif'"
1151-msgstr "「%ls」に対応する「if」または「elif」がありません"
1152-
1153-#: parser.c:2741
1154-#, c-format
1155-msgid "encountered `%ls' without a matching `for', `while', or `until'"
1156-msgstr "「%ls」に対応する「for」「while」または「until」がありません"
1157-
1158-#: parser.c:2745
1159-#, c-format
1160-msgid "encountered `%ls' without a matching `do'"
1161-msgstr "「%ls」に対応する「do」がありません"
1162-
1163-#: parser.c:2750
1164-#, c-format
1165-msgid "encountered `%ls' without a matching `case'"
1166-msgstr "「%ls」に対応する「case」がありません"
1167-
1168-#: parser.c:2766
1169-#, c-format
1170-msgid "(maybe you missed `%ls'?)"
1171-msgstr "(「%ls」を忘れていませんか?)"
1172-
11731181 #: path.c:1021
11741182 msgid "$HOME is not set"
11751183 msgstr "$HOME が設定されていません"
11761184
1177-#: path.c:1030 variable.c:3029
1185+#: path.c:1030 variable.c:3043
11781186 msgid "$OLDPWD is not set"
11791187 msgstr "$OLDPWD が設定されていません"
11801188
@@ -1282,7 +1290,7 @@
12821290 msgid "redirection: invalid file descriptor"
12831291 msgstr "リダイレクト: 無効なファイル記述子です"
12841292
1285-#: redir.c:331 redir.c:609 redir.c:681
1293+#: redir.c:331 redir.c:639 redir.c:711
12861294 #, c-format
12871295 msgid "redirection: file descriptor %d is unavailable"
12881296 msgstr "リダイレクト: ファイル記述子 %d は使えません"
@@ -1297,53 +1305,53 @@
12971305 msgid "cannot save file descriptor %d"
12981306 msgstr "ファイル記述子 %d を保存できません"
12991307
1300-#: redir.c:559
1308+#: redir.c:589
13011309 #, c-format
13021310 msgid "socket redirection: cannot resolve the address of `%s': %s"
13031311 msgstr "ソケットリダイレクト:「%s」のアドレスを解決できません: %s"
13041312
1305-#: redir.c:603 redir.c:618 redir.c:673
1313+#: redir.c:633 redir.c:648 redir.c:703
13061314 #, c-format
13071315 msgid "redirection: %s"
13081316 msgstr "リダイレクト: %s"
13091317
1310-#: redir.c:626
1318+#: redir.c:656
13111319 #, c-format
13121320 msgid "redirection: file descriptor %d is not readable"
13131321 msgstr "リダイレクト: ファイル記述子 %d は読み込み可能ではありません"
13141322
1315-#: redir.c:638
1323+#: redir.c:668
13161324 #, c-format
13171325 msgid "redirection: file descriptor %d is not writable"
13181326 msgstr "リダイレクト: ファイル記述子 %d は書き込み可能ではありません"
13191327
1320-#: redir.c:676
1328+#: redir.c:706
13211329 #, c-format
13221330 msgid "redirection: %d>>|%d: the input and output file descriptors are same"
13231331 msgstr "リダイレクト: %d>>|%d: 入力側と出力側のファイル記述子が同じです"
13241332
1325-#: redir.c:720
1333+#: redir.c:750
13261334 #, c-format
13271335 msgid "redirection: %d>>|%d"
13281336 msgstr "リダイレクト: %d>>|%d"
13291337
1330-#: redir.c:738 redir.c:778 redir.c:799
1338+#: redir.c:768 redir.c:808 redir.c:829
13311339 msgid "cannot write the here-document contents to the temporary file"
13321340 msgstr "ヒアドキュメントの内容を一時ファイルに書き出せません"
13331341
1334-#: redir.c:791
1342+#: redir.c:821
13351343 msgid "cannot create a temporary file for the here-document"
13361344 msgstr "ヒアドキュメント用の一時ファイルを作成できません"
13371345
1338-#: redir.c:804
1346+#: redir.c:834
13391347 msgid "cannot seek the temporary file for the here-document"
13401348 msgstr "ヒアドキュメント用の一時ファイルのシークができません"
13411349
1342-#: redir.c:818
1350+#: redir.c:848
13431351 msgid "redirection: cannot open a pipe for the process redirection"
13441352 msgstr "リダイレクト: プロセスリダイレクト用のパイプを開けません"
13451353
1346-#: redir.c:857
1354+#: redir.c:887
13471355 msgid "process redirection"
13481356 msgstr "プロセスリダイレクト"
13491357
@@ -1414,107 +1422,107 @@
14141422 msgid "unknown error"
14151423 msgstr "不明なエラー"
14161424
1417-#: variable.c:373 variable.c:378
1425+#: variable.c:372 variable.c:377
14181426 msgid "failed to set $PWD"
14191427 msgstr "$PWD の設定に失敗しました"
14201428
1421-#: variable.c:402
1429+#: variable.c:401
14221430 #, c-format
14231431 msgid "no such array $%ls"
14241432 msgstr "$%ls というような配列はありません"
14251433
1426-#: variable.c:405 variable.c:607 variable.c:1709 variable.c:2317
1427-#: variable.c:3075
1434+#: variable.c:404 variable.c:606 variable.c:1722 variable.c:2331
1435+#: variable.c:3089
14281436 #, c-format
14291437 msgid "$%ls is read-only"
14301438 msgstr "$%ls は読み込み専用です"
14311439
1432-#: variable.c:422
1440+#: variable.c:421
14331441 #, c-format
14341442 msgid "failed to unset environment variable $%s"
14351443 msgstr "環境変数 $%s の削除に失敗しました"
14361444
1437-#: variable.c:426
1445+#: variable.c:425
14381446 #, c-format
14391447 msgid "failed to set environment variable $%s"
14401448 msgstr "環境変数 $%s の設定に失敗しました"
14411449
1442-#: variable.c:701
1450+#: variable.c:700
14431451 #, c-format
14441452 msgid "index %zu is out of range (the actual size of array $%ls is %zu)"
14451453 msgstr "インデックス %zu は範囲外です (配列 $%ls のサイズは %zu です)"
14461454
1447-#: variable.c:1244
1455+#: variable.c:1246
14481456 #, c-format
14491457 msgid "function `%ls' cannot be redefined because it is read-only"
14501458 msgstr "関数「%ls」は読み込み専用なので再定義できません"
14511459
1452-#: variable.c:1424 variable.c:3161
1460+#: variable.c:1426 variable.c:3175
14531461 msgid "the directory stack is empty"
14541462 msgstr "ディレクトリスタックは空です"
14551463
1456-#: variable.c:1462 variable.c:3013 variable.c:3257
1464+#: variable.c:1464 variable.c:3027 variable.c:3271
14571465 msgid "$PWD is not set"
14581466 msgstr "$PWD が設定されていません"
14591467
1460-#: variable.c:1474
1468+#: variable.c:1476
14611469 #, c-format
14621470 msgid "index %ls is out of range"
14631471 msgstr "インデックス %ls は範囲外です"
14641472
1465-#: variable.c:1733
1473+#: variable.c:1746
14661474 #, c-format
14671475 msgid "no such variable $%ls"
14681476 msgstr "$%ls というような変数はありません"
14691477
1470-#: variable.c:1748
1478+#: variable.c:1761
14711479 #, c-format
14721480 msgid "no such function `%ls'"
14731481 msgstr "「%ls」というような関数はありません"
14741482
1475-#: variable.c:1937
1483+#: variable.c:1951
14761484 msgid "set or print variables"
14771485 msgstr "変数を設定または表示する"
14781486
1479-#: variable.c:1940
1487+#: variable.c:1954
14801488 msgid "\ttypeset [-fgprxX] [name[=value]...]\n"
14811489 msgstr "\ttypeset [-fgprxX] [名前[=値]...]\n"
14821490
1483-#: variable.c:1943
1491+#: variable.c:1957
14841492 msgid "export variables as environment variables"
14851493 msgstr "変数を環境変数としてエクスポートする"
14861494
1487-#: variable.c:1946
1495+#: variable.c:1960
14881496 msgid "\texport [-prX] [name[=value]...]\n"
14891497 msgstr "\texport [-prX] [名前[=値]...]\n"
14901498
1491-#: variable.c:1949
1499+#: variable.c:1963
14921500 msgid "make variables read-only"
14931501 msgstr "変数を読み込み専用にする"
14941502
1495-#: variable.c:1952
1503+#: variable.c:1966
14961504 msgid "\treadonly [-fpxX] [name[=value]...]\n"
14971505 msgstr "\treadonly [-fpxX] [名前[=値]...]\n"
14981506
1499-#: variable.c:2000
1507+#: variable.c:2014
15001508 msgid "more than one option cannot be used at once"
15011509 msgstr "二つ以上のオプションを同時に使うことはできません"
15021510
1503-#: variable.c:2019
1511+#: variable.c:2033
15041512 #, c-format
15051513 msgid "`%ls' is not a valid array name"
15061514 msgstr "「%ls」は有効な配列名ではありません"
15071515
1508-#: variable.c:2211
1516+#: variable.c:2225
15091517 #, c-format
15101518 msgid "index %ls is out of range (the actual size of array $%ls is %zu)"
15111519 msgstr "インデックス %ls は範囲外です (配列 $%ls のサイズは %zu です)"
15121520
1513-#: variable.c:2218
1521+#: variable.c:2232
15141522 msgid "manipulate an array"
15151523 msgstr "配列を操作する"
15161524
1517-#: variable.c:2221
1525+#: variable.c:2235
15181526 msgid ""
15191527 "\tarray # print arrays\n"
15201528 "\tarray name [value...] # set array values\n"
@@ -1528,30 +1536,30 @@
15281536 "\tarray -i 名前 インデックス [値...]\n"
15291537 "\tarray -s 名前 インデックス 値\n"
15301538
1531-#: variable.c:2292
1539+#: variable.c:2306
15321540 #, c-format
15331541 msgid "function `%ls' is read-only"
15341542 msgstr "関数「%ls」は読み込み専用です"
15351543
1536-#: variable.c:2328
1544+#: variable.c:2342
15371545 msgid "remove variables or functions"
15381546 msgstr "変数または関数を削除する"
15391547
1540-#: variable.c:2331
1548+#: variable.c:2345
15411549 msgid "\tunset [-fv] [name...]\n"
15421550 msgstr "\tunset [-fv] 名前...\n"
15431551
1544-#: variable.c:2374
1552+#: variable.c:2388
15451553 #, c-format
15461554 msgid "%ls: the operand value must not be negative"
15471555 msgstr "%ls: 負でないオペランドの値を指定してください"
15481556
1549-#: variable.c:2390 variable.c:3072
1557+#: variable.c:2404 variable.c:3086
15501558 #, c-format
15511559 msgid "$%ls is not an array"
15521560 msgstr "$%ls は配列ではありません"
15531561
1554-#: variable.c:2407
1562+#: variable.c:2421
15551563 #, c-format
15561564 msgid "%ld: cannot shift so many (there is only one positional parameter)"
15571565 msgid_plural ""
@@ -1559,86 +1567,86 @@
15591567 msgstr[0] ""
15601568 "%ld: シフトする数が多すぎます (位置パラメータは %zu 個しかありません)"
15611569
1562-#: variable.c:2413
1570+#: variable.c:2427
15631571 #, c-format
15641572 msgid "%ld: cannot shift so many (there is only one array element)"
15651573 msgid_plural "%ld: cannot shift so many (there are only %zu array elements)"
15661574 msgstr[0] "%ld: シフトする数が多すぎます (配列の要素は %zu 個しかありません)"
15671575
1568-#: variable.c:2437
1576+#: variable.c:2451
15691577 msgid "remove some positional parameters or array elements"
15701578 msgstr "位置パラメータまたは配列の要素の一部を削除する"
15711579
1572-#: variable.c:2440
1580+#: variable.c:2454
15731581 msgid "\tshift [-A array_name] [count]\n"
15741582 msgstr "\tshift [-A 配列名] [個数]\n"
15751583
1576-#: variable.c:2471 variable.c:2701
1584+#: variable.c:2485 variable.c:2715
15771585 #, c-format
15781586 msgid "`%ls' is not a valid variable name"
15791587 msgstr "「%ls」は有効な変数名ではありません"
15801588
1581-#: variable.c:2474
1589+#: variable.c:2488
15821590 #, c-format
15831591 msgid "`%ls' is not a valid option specification"
15841592 msgstr "「%ls」は有効なオプション指定文字列ではありません"
15851593
1586-#: variable.c:2532
1594+#: variable.c:2546
15871595 #, c-format
15881596 msgid "%ls: `-%lc' is not a valid option\n"
15891597 msgstr "%ls: 「%lc」は有効なオプションではありません\n"
15901598
1591-#: variable.c:2555
1599+#: variable.c:2569
15921600 #, c-format
15931601 msgid "%ls: the -%lc option's argument is missing\n"
15941602 msgstr "%ls: -%lc オプションの引数が抜けています\n"
15951603
1596-#: variable.c:2578
1604+#: variable.c:2592
15971605 msgid "$OPTIND has an invalid value"
15981606 msgstr "$OPTIND の値が正しくありません"
15991607
1600-#: variable.c:2633
1608+#: variable.c:2647
16011609 msgid "parse command options"
16021610 msgstr "コマンドのオプションを解析する"
16031611
1604-#: variable.c:2636
1612+#: variable.c:2650
16051613 msgid "\tgetopts options variable [argument...]\n"
16061614 msgstr "\tgetopts オプション 変数名 [引数...]\n"
16071615
1608-#: variable.c:2944
1616+#: variable.c:2958
16091617 msgid "read a line from the standard input"
16101618 msgstr "標準入力から行を読み込む"
16111619
1612-#: variable.c:2947
1620+#: variable.c:2961
16131621 msgid "\tread [-Aer] [-P|-p] variable...\n"
16141622 msgstr "\tread [-Aer] [-P|-p] 変数名...\n"
16151623
1616-#: variable.c:3127
1624+#: variable.c:3141
16171625 msgid "push a directory into the directory stack"
16181626 msgstr "ディレクトリスタックにディレクトリを追加する"
16191627
1620-#: variable.c:3130
1628+#: variable.c:3144
16211629 msgid "\tpushd [-L|-P] [directory]\n"
16221630 msgstr "\tpushd [-L|-P] [ディレクトリ]\n"
16231631
1624-#: variable.c:3170 variable.c:3246
1632+#: variable.c:3184 variable.c:3260
16251633 #, c-format
16261634 msgid "`%ls' is not a valid index"
16271635 msgstr "「%ls」は有効なインデックスではありません"
16281636
1629-#: variable.c:3193
1637+#: variable.c:3207
16301638 msgid "pop a directory from the directory stack"
16311639 msgstr "ディレクトリスタックからディレクトリを削除する"
16321640
1633-#: variable.c:3196
1641+#: variable.c:3210
16341642 msgid "\tpopd [index]\n"
16351643 msgstr "\tpopd [インデックス]\n"
16361644
1637-#: variable.c:3284
1645+#: variable.c:3298
16381646 msgid "print the directory stack"
16391647 msgstr "ディレクトリスタックを表示する"
16401648
1641-#: variable.c:3287
1649+#: variable.c:3301
16421650 msgid "\tdirs [-cv] [index...]\n"
16431651 msgstr "\tdirs [-cv] [インデックス...]\n"
16441652
Show on old repository browser