ctags Internal API¶
Input text stream¶
Function prototypes for handling input text stream are declared in
main/read.h
. The file exists in Exuberant Ctags, too. However, the
names functions are changed when overhauling --line-directive
option. (In addition macros were converted to functions for making
data structures for the input text stream opaque.)
Ctags has 3 groups of functions for handling input: input, bypass, and raw. Parser developers should use input group. The rest of two are for ctags main part.
inputFile type and the functions of input group¶
Note
The original version of this section was written
before inputFile
type and File
variable are made private.
inputFile
is the type for representing the input file and stream for
a parser. It was declared in main/read.h
but now it is defined in
main/read.c
.
Ctags uses a file static variable File
having type inputFile
for
maintaining the input file and stream. File
is also defined in
main/read.c as inputFile
is.
fp
and line
are the essential fields of File
. fp
having type
well known MIO
declared in main/mio.h
. By calling functions of input group
(getcFromInputFile
and readLineFromInputFile
), a parser gets input
text from fp
.
The functions of input group updates fields input
and source
of File
variable.
These two fields has type inputFileInfo
. These two fields are for mainly
tracking the name of file and the current line number. Usually ctags uses
only input
field. source
field is used only when #line
directive is found
in the current input text stream.
A case when a tool generates the input file from another file, a tool
can record the original source file to the generated file with using
the #line
directive. source
field is used for tracking/recording the
information appeared on #line
directives.
Regex pattern matching are also done behind calling the functions of this group.
The functions of bypass group¶
The functions of bypass group (readLineFromBypass
and
readLineFromBypassSlow
) are used for reading text from fp
field of
File
static variable without updating input
and source
fields of
File
variable.
Parsers may not need the functions of this group. The functions are used in ctags main part. The functions are used to make pattern fields of tags file, for example.
The functions of raw group¶
The functions of this group (readLineRaw
and readLineRawWithNoSeek
)
take a parameter having type MIO
; and don’t touch File
static
variable.
Parsers may not need the functions of this group. The functions are used in ctags main part. The functions are used to load option files, for example.
promise API¶
Note
Currently the tagging via promise API is disabled by default.
Use --extras=+g
option for enabling it.
Background and Idea¶
More than one programming languages can be used in one input text stream. promise API allows a host parser running a guest parser in the specified area of input text stream.
e.g. Code written in c language (C code) is embedded in code written in Yacc language (Yacc code). Let’s think about this input stream.
/* foo.y */
%token
END_OF_FILE 0
ERROR 255
BELL 1
%{
/* C language */
int counter;
%}
%right EQUALS
%left PLUS MINUS
...
%%
CfgFile : CfgEntryList
{ InterpretConfigs($1); }
;
...
%%
int
yyerror(char *s)
{
(void)fprintf(stderr,"%s: line %d of %s\n",s,lineNum,
(scanFile?scanFile:"(unknown)"));
if (scanStr)
(void)fprintf(stderr,"last scanned symbol is: %s\n",scanStr);
return 1;
}
In the input the area started from %{
to %}
and the area started from
the second %%
to the end of file are written in C. Yacc can be called
host language, and C can be called guest language.
Ctags may choose the Yacc parser for the input. However, the parser doesn’t know about C syntax. Implementing C parser in the Yacc parser is one of approach. However, ctags has already C parser. The Yacc parser should utilize the existing C parser. The promise API allows this.
More examples are in “Applying a parser to specified areas of input file”.
Usage¶
See a commit titled with “Yacc: run C parser in the areas where code is written in C”. I applied promise API to the Yacc parser.
The parser for host language must track and record the start
and the
end
of a guest language. Pairs of line number
and byte offset
represents the start
and end
. When the start
and end
are
fixed, call makePromise
with (1) the guest parser name, (2) start
,
and (3) end
. (This description is a bit simplified the real usage.)
Let’s see the actual code from parsers/yacc.c
.
struct cStart {
unsigned long input;
unsigned long source;
};
Both fields are for recording start
. input
field
is for recording the value returned from getInputLineNumber
.
source
is for getSourceLineNumber
. See “inputFile” for the
difference of the two.
enter_c_prologue
shown in the next is a function called when %{
is
found in the current input text stream. Remember, in yacc syntax, %{
is a marker of C code area.
static void enter_c_prologue (const char *line CTAGS_ATTR_UNUSED,
const regexMatch *matches CTAGS_ATTR_UNUSED,
unsigned int count CTAGS_ATTR_UNUSED,
void *data)
{
struct cStart *cstart = data;
readLineFromInputFile ();
cstart->input = getInputLineNumber ();
cstart->source = getSourceLineNumber ();
}
The function just records the start line. It calls
readLineFromInputFile
because the C code may start the next line of
the line where the marker is.
leave_c_prologue
shown in the next is a function called when %}
,
the end marker of C code area, is found in the current input text stream.
static void leave_c_prologue (const char *line CTAGS_ATTR_UNUSED,
const regexMatch *matches CTAGS_ATTR_UNUSED,
unsigned int count CTAGS_ATTR_UNUSED,
void *data)
{
struct cStart *cstart = data;
unsigned long c_end;
c_end = getInputLineNumber ();
makePromise ("C", cstart->input, 0, c_end, 0, cstart->source);
}
After recording the line number of the end of the C code area,
leave_c_prologue
calls makePromise
.
Of course "C"
stands for C language, the name of guest parser.
Available parser names can be listed by running ctags with
--list-languages
option. In this example two 0
characters are provided as
the 3rd and 5th argument. They are byte offsets of the start and the end of the
C language area from the beginning of the line which is 0 in this case. In
general, the guest language’s section does not have to start at the beginning of
the line in which case the two offsets have to be provided. Compilers reading
the input character by character can obtain the current offset by calling
getInputLineOffset()
.
Internal design¶
A host parser cannot run a guest parser directly. What the host parser
can do is just asking the ctags main part scheduling of running the
guest parser for specified area which defined with the start
and
end
. These scheduling requests are called promises.
After running the host parser, before closing the input stream, the ctags main part checks the existence of promise(s). If there is, the main part makes a sub input stream and run the guest parser specified in the promise. The sub input stream is made from the original input stream by narrowing as requested in the promise. The main part iterates the above process till there is no promise.
Theoretically a guest parser can be nested; it can make a promise. The level 2 guest is also just scheduled. (However, I have never tested such a nested guest parser).
Why not running the guest parser directly from the context of the host parser? Remember many parsers have their own file static variables. If a parser is called from the parser, the variables may be crashed.
Automatic parser guessing¶
Managing regular expression parsers¶
Parser written in C¶
tokenInfo API¶
In Exuberant Ctags, a developer can write a parser anyway; only input stream and tagEntryInfo data structure is given.
However, while maintaining Universal Ctags I (Masatake YAMATO) think we should have a framework for writing parser. Of course the framework is optional; you can still write a parser without the framework.
To design a framework, I have studied how @b4n (Colomban Wendling) writes parsers. tokenInfo API is the first fruit of my study.
TBW
Output tag stream¶
Ctags provides makeTagEntry
to parsers as an entry point for writing
tag information to MIO. makeTagEntry
calls writeTagEntry
if the
parser does not set useCork
field. writeTagEntry
calls writerWriteTag
.
writerWriteTag
just calls writeEntry
of writer backends.
writerTable
variable holds the four backends: ctagsWriter, etagsWriter,
xrefWriter, and jsonWriter.
One of them is chosen depending on the arguments passed to ctags.
If useCork
is set, the tag information goes to a queue on memory.
The queue is flushed when useCork
in unset. See “cork API” for more
details.
cork API¶
Background and Idea¶
cork API is introduced for recording scope information easier.
Before introducing cork API, a scope information must be recorded as
strings. It is flexible but memory management is required.
Following code is taken from clojure.c
(with some modifications).
if (vStringLength (parent) > 0)
{
current.extensionFields.scope[0] = ClojureKinds[K_NAMESPACE].name;
current.extensionFields.scope[1] = vStringValue (parent);
}
makeTagEntry (¤t);
parent
, scope [0]
and scope [1]
are vStrings. The parser must manage
their life cycles; the parser cannot free them till the tag referring them via
its scope fields are emitted, and must free them after emitting.
cork API provides more solid way to hold scope information. cork API
expects parent
, which represents scope of a tag(current
)
currently parser dealing, is recorded to a tags file before recording
the current
tag via makeTagEntry
function.
For passing the information about parent
to makeTagEntry
,
tagEntryInfo
object was created. It was used just for recording; and
freed after recording. In cork API, it is not freed after recording;
a parser can reused it as scope information.
How to use¶
See a commit titled with “clojure: use cork”. I applied cork API to the clojure parser.
Cork API can be enabled and disabled per parser, and is disabled by default. So there is no impact till you enables it in your parser.
useCork
field is introduced in parserDefinition
type:
typedef struct {
...
unsigned int useCork;
...
} parserDefinition;
Set CORK_QUEUE
to useCork
like:
extern parserDefinition *ClojureParser (void)
{
...
parserDefinition *def = parserNew ("Clojure");
...
def->useCork = CORK_QUEUE;
return def;
}
When ctags running a parser with useCork
being CORK_QUEUE
, all output
requested via makeTagEntry
function calling is stored to an internal
queue, not to tags
file. When parsing an input file is done, the
tag information stored automatically to the queue are flushed to
tags
file in batch.
When calling makeTagEntry
with a tagEntryInfo
object (parent
),
it returns an integer. The integer can be used as handle for referring
the object after calling.
static int parent = CORK_NIL;
...
parent = makeTagEntry (&e);
The handle can be used by setting to a scopeIndex
field of current
tag, which is in the scope of parent
.
current.extensionFields.scopeIndex = parent;
When passing current
to makeTagEntry
, the scopeIndex
is
referred for emitting the scope information of current
.
scopeIndex
must be set to CORK_NIL
if a tag is not in any scope.
When using scopeIndex
of current
, NULL
must be assigned to both
current.extensionFields.scope[0]
and
current.extensionFields.scope[1]
. initTagEntry
function does this
initialization internally, so you generally you don’t have to write
the initialization explicitly.
Automatic full qualified tag generation¶
If a parser uses the cork API for recording and emitting scope
information, ctags can reuse it for generating full qualified (FQ)
tags. Set requestAutomaticFQTag
field of parserDefinition
to
TRUE
then the main part of ctags emits FQ tags on behalf of the parser
if --extras=+q
is given.
An example can be found in DTS parser:
extern parserDefinition* DTSParser (void)
{
static const char *const extensions [] = { "dts", "dtsi", NULL };
parserDefinition* const def = parserNew ("DTS");
...
def->requestAutomaticFQTag = TRUE;
return def;
}
Setting requestAutomaticFQTag
to TRUE
implies setting
useCork
to CORK_QUEUE
.
PackCC compiler-compiler¶
PackCC is a compiler-compiler; it translates .peg
grammar file to .c
file. PackCC was originally written by Arihiro Yoshida. Its source
repository is at https://github.com/arithy/packcc.
The source tree of PackCC is grafted at misc/packcc
directory.
Building PackCC and ctags are integrated in the build-scripts of
Universal Ctags.
Refer peg/valink.peg as a sample of a parser using PackCC.