yarp 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/CONTRIBUTING.md +51 -0
- data/LICENSE.md +7 -0
- data/Makefile.in +79 -0
- data/README.md +86 -0
- data/config.h.in +25 -0
- data/config.yml +2147 -0
- data/configure +4487 -0
- data/docs/build_system.md +85 -0
- data/docs/building.md +26 -0
- data/docs/configuration.md +56 -0
- data/docs/design.md +53 -0
- data/docs/encoding.md +116 -0
- data/docs/extension.md +20 -0
- data/docs/fuzzing.md +93 -0
- data/docs/heredocs.md +36 -0
- data/docs/mapping.md +117 -0
- data/docs/ripper.md +36 -0
- data/docs/serialization.md +130 -0
- data/docs/testing.md +55 -0
- data/ext/yarp/api_node.c +3680 -0
- data/ext/yarp/api_pack.c +256 -0
- data/ext/yarp/extconf.rb +131 -0
- data/ext/yarp/extension.c +547 -0
- data/ext/yarp/extension.h +18 -0
- data/include/yarp/ast.h +1412 -0
- data/include/yarp/defines.h +54 -0
- data/include/yarp/diagnostic.h +24 -0
- data/include/yarp/enc/yp_encoding.h +94 -0
- data/include/yarp/node.h +36 -0
- data/include/yarp/pack.h +141 -0
- data/include/yarp/parser.h +389 -0
- data/include/yarp/regexp.h +19 -0
- data/include/yarp/unescape.h +42 -0
- data/include/yarp/util/yp_buffer.h +39 -0
- data/include/yarp/util/yp_char.h +75 -0
- data/include/yarp/util/yp_constant_pool.h +64 -0
- data/include/yarp/util/yp_list.h +67 -0
- data/include/yarp/util/yp_memchr.h +14 -0
- data/include/yarp/util/yp_newline_list.h +54 -0
- data/include/yarp/util/yp_state_stack.h +24 -0
- data/include/yarp/util/yp_string.h +57 -0
- data/include/yarp/util/yp_string_list.h +28 -0
- data/include/yarp/util/yp_strpbrk.h +29 -0
- data/include/yarp/version.h +5 -0
- data/include/yarp.h +69 -0
- data/lib/yarp/lex_compat.rb +759 -0
- data/lib/yarp/node.rb +7428 -0
- data/lib/yarp/pack.rb +185 -0
- data/lib/yarp/ripper_compat.rb +174 -0
- data/lib/yarp/serialize.rb +389 -0
- data/lib/yarp.rb +330 -0
- data/src/diagnostic.c +25 -0
- data/src/enc/yp_big5.c +79 -0
- data/src/enc/yp_euc_jp.c +85 -0
- data/src/enc/yp_gbk.c +88 -0
- data/src/enc/yp_shift_jis.c +83 -0
- data/src/enc/yp_tables.c +509 -0
- data/src/enc/yp_unicode.c +2320 -0
- data/src/enc/yp_windows_31j.c +83 -0
- data/src/node.c +2011 -0
- data/src/pack.c +493 -0
- data/src/prettyprint.c +1782 -0
- data/src/regexp.c +580 -0
- data/src/serialize.c +1576 -0
- data/src/token_type.c +347 -0
- data/src/unescape.c +576 -0
- data/src/util/yp_buffer.c +78 -0
- data/src/util/yp_char.c +229 -0
- data/src/util/yp_constant_pool.c +147 -0
- data/src/util/yp_list.c +50 -0
- data/src/util/yp_memchr.c +31 -0
- data/src/util/yp_newline_list.c +119 -0
- data/src/util/yp_state_stack.c +25 -0
- data/src/util/yp_string.c +207 -0
- data/src/util/yp_string_list.c +32 -0
- data/src/util/yp_strncasecmp.c +20 -0
- data/src/util/yp_strpbrk.c +66 -0
- data/src/yarp.c +13211 -0
- data/yarp.gemspec +100 -0
- metadata +125 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
# Serialization
|
2
|
+
|
3
|
+
YARP ships with the ability to serialize a syntax tree to a single string. The string can then be deserialized back into a syntax tree using a language other than C. This is useful for using the parsing logic in other tools without having to write a parser in that language. The syntax tree still requires a copy of the original source, as for the most part it just contains byte offsets into the source string.
|
4
|
+
|
5
|
+
## Structure
|
6
|
+
|
7
|
+
The serialized string representing the syntax tree is composed of three parts: the header, the body, and the constant pool. The header contains information like the version of YARP that serialized the tree. The body contains the actual nodes in the tree. The constant pool contains constants that were interned while parsing.
|
8
|
+
|
9
|
+
The header is structured like the following table:
|
10
|
+
|
11
|
+
| # bytes | field |
|
12
|
+
| --- | --- |
|
13
|
+
| `4` | "YARP" |
|
14
|
+
| `1` | major version number |
|
15
|
+
| `1` | minor version number |
|
16
|
+
| `1` | patch version number |
|
17
|
+
| varint | the length of the encoding name |
|
18
|
+
| string | the encoding name |
|
19
|
+
| varint | number of errors |
|
20
|
+
| varint | byte length of error |
|
21
|
+
| string | error string, as byte[] in source encoding |
|
22
|
+
| varint | location in the source code - start |
|
23
|
+
| varint | location in the source code - length |
|
24
|
+
| ... | more errors |
|
25
|
+
| varint | number of warnings |
|
26
|
+
| varint | byte length of warning |
|
27
|
+
| string | warning string, as byte[] in source encoding |
|
28
|
+
| varint | location in the source code - start |
|
29
|
+
| varint | location in the source code - length |
|
30
|
+
| ... | more warnings |
|
31
|
+
| `4` | content pool offset |
|
32
|
+
| varint | content pool size |
|
33
|
+
|
34
|
+
After the header comes the body of the serialized string. The body consistents of a sequence of nodes that is built using a prefix traversal order of the syntax tree. Each node is structured like the following table:
|
35
|
+
|
36
|
+
| # bytes | field |
|
37
|
+
| --- | --- |
|
38
|
+
| `1` | node type |
|
39
|
+
| varint | byte offset into the source string where this node begins |
|
40
|
+
| varint | length of the node in bytes in the source string |
|
41
|
+
|
42
|
+
Each node's child is then appended to the serialized string. The child node types can be determined by referencing `config.yml`. Depending on the type of child node, it could take a couple of different forms, described below:
|
43
|
+
|
44
|
+
* `node` - A child node that is a node itself. This is structured just as like parent node.
|
45
|
+
* `node?` - A child node that is optionally present. If the node is not present, then a single `0` byte will be written in its place. If it is present, then it will be structured just as like parent node.
|
46
|
+
* `node[]` - A child node that is an array of nodes. This is structured as a variable-length integer length, followed by the child nodes themselves.
|
47
|
+
* `string` - A child node that is a string. For example, this is used as the name of the method in a call node, since it cannot directly reference the source string (as in `@-` or `foo=`). This is structured as a variable-length integer byte length, followed by the string itself (_without_ a trailing null byte).
|
48
|
+
* `constant` - A variable-length integer that represents an index in the constant pool.
|
49
|
+
* `constant[]` - A child node that is an array of constants. This is structured as a variable-length integer length, followed by the child constants themselves.
|
50
|
+
* `location` - A child node that is a location. This is structured as a variable-length integer start followed by a variable-length integer length.
|
51
|
+
* `location?` - A child node that is a location that is optionally present. If the location is not present, then a single `0` byte will be written in its place. If it is present, then it will be structured just like the `location` child node.
|
52
|
+
* `location[]` - A child node that is an array of locations. This is structured as a `4` byte length, followed by the locations themselves.
|
53
|
+
* `uint32` - A child node that is a 32-bit unsigned integer. This is structured as a variable-length integer.
|
54
|
+
|
55
|
+
After the syntax tree, the content pool is serialized. This is a list of constants that were referenced from within the tree. The content pool begins at the offset specified in the header. Each constant is structured as:
|
56
|
+
|
57
|
+
| # bytes | field |
|
58
|
+
| --- | --- |
|
59
|
+
| `4` | the byte offset in the source |
|
60
|
+
| `4` | the byte length in the source |
|
61
|
+
|
62
|
+
At the end of the serialization, the buffer is null terminated.
|
63
|
+
|
64
|
+
## Variable-length integers
|
65
|
+
|
66
|
+
Variable-length integers are used throughout the serialized format, using the [LEB128](https://en.wikipedia.org/wiki/LEB128) encoding. This drastically cuts down on the size of the serialized string, especially when the source file is large.
|
67
|
+
|
68
|
+
## APIs
|
69
|
+
|
70
|
+
The relevant APIs and struct definitions are listed below:
|
71
|
+
|
72
|
+
```c
|
73
|
+
// A yp_buffer_t is a simple memory buffer that stores data in a contiguous
|
74
|
+
// block of memory. It is used to store the serialized representation of a
|
75
|
+
// YARP tree.
|
76
|
+
typedef struct {
|
77
|
+
char *value;
|
78
|
+
size_t length;
|
79
|
+
size_t capacity;
|
80
|
+
} yp_buffer_t;
|
81
|
+
|
82
|
+
// Initialize a yp_buffer_t with its default values.
|
83
|
+
bool yp_buffer_init(yp_buffer_t *);
|
84
|
+
|
85
|
+
// Free the memory associated with the buffer.
|
86
|
+
void yp_buffer_free(yp_buffer_t *);
|
87
|
+
|
88
|
+
// Parse and serialize the AST represented by the given source to the given
|
89
|
+
// buffer.
|
90
|
+
void yp_parse_serialize(const char *, size_t, yp_buffer_t *, const char *);
|
91
|
+
```
|
92
|
+
|
93
|
+
Typically you would use a stack-allocated `yp_buffer_t` and call `yp_parse_serialize`, as in:
|
94
|
+
|
95
|
+
```c
|
96
|
+
void
|
97
|
+
serialize(const char *source, size_t length) {
|
98
|
+
yp_buffer_t buffer;
|
99
|
+
if (!yp_buffer_init(&buffer)) return;
|
100
|
+
|
101
|
+
yp_parse_serialize(source, length, &buffer, NULL);
|
102
|
+
// Do something with the serialized string.
|
103
|
+
|
104
|
+
yp_buffer_free(&buffer);
|
105
|
+
}
|
106
|
+
```
|
107
|
+
|
108
|
+
The final argument to `yp_parse_serialize` controls the metadata of the source. This includes the filepath that the source is associated with, and any nested local variables scopes that are necessary to properly parse the file (in the case of parsing an `eval`). The metadata is a serialized format itself, and is structured as follows:
|
109
|
+
|
110
|
+
| # bytes | field |
|
111
|
+
| --- | --- |
|
112
|
+
| `4` | the size of the filepath string |
|
113
|
+
| | the filepath string |
|
114
|
+
| `4` | the number of local variable scopes |
|
115
|
+
|
116
|
+
Then, each local variable scope is encoded as:
|
117
|
+
|
118
|
+
| # bytes | field |
|
119
|
+
| --- | --- |
|
120
|
+
| `4` | the number of local variables in the scope |
|
121
|
+
| | the local variables |
|
122
|
+
|
123
|
+
Each local variable within each scope is encoded as:
|
124
|
+
|
125
|
+
| # bytes | field |
|
126
|
+
| --- | --- |
|
127
|
+
| `4` | the size of the local variable name |
|
128
|
+
| | the local variable name |
|
129
|
+
|
130
|
+
The metadata can be `NULL` (as seen in the example above). If it is not null, then a minimal metadata string would be `"\0\0\0\0\0\0\0\0"` which would use 4 bytes to indicate an empty filepath string and 4 bytes to indicate that there were no local variable scopes.
|
data/docs/testing.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Testing
|
2
|
+
|
3
|
+
This document explains how to test YARP, both locally, and against existing test suites.
|
4
|
+
|
5
|
+
## Test suite
|
6
|
+
|
7
|
+
`rake test` will run all of the files in the `test/` directory. This can be conceived of as two parts: unit tests, and snapshot tests.
|
8
|
+
|
9
|
+
### Unit tests
|
10
|
+
|
11
|
+
These test specific YARP implementation details like comments, errors, and regular expressions. There are corresponding files for each thing being tested (like `test/errors_test.rb`).
|
12
|
+
|
13
|
+
### Snapshot tests
|
14
|
+
|
15
|
+
Snapshot tests ensure that parsed output is equivalent to previous parsed output. There are many categorized examples of valid syntax within the `test/fixtures/` directory. When the test suite runs, it will parse all of this syntax, and compare it against corresponding files in the `test/snapshots/` directory. For example, `test/fixtures/strings.txt` has a corresponding `test/snapshots/strings.txt`.
|
16
|
+
|
17
|
+
If the parsed files do not match, it will raise an error. If there is not a corresponding file in the `test/snapshots/` directory, one will be created so that it exists for the next test run.
|
18
|
+
|
19
|
+
### Testing against repositories
|
20
|
+
|
21
|
+
To test the parser against a repository, you can run `FILEPATHS='/path/to/repository/**/*.rb' rake lex`. This will run the parser against every file matched by the glob pattern and check its generated tokens against those generated by ripper.
|
22
|
+
|
23
|
+
## Local testing
|
24
|
+
|
25
|
+
As you are working, you will likely want to test your code locally. `test.rb` is ignored by git, so it can be used for local testing. There are also two executables which may help you:
|
26
|
+
|
27
|
+
1. **bin/lex** takes a filepath and compares YARP's lexed output to Ripper's lexed output. It prints any lexed output that doesn't match. It does some minor transformations to the lexed output in order to compare them, like split YARP's heredoc tokens to mirror Ripper's.
|
28
|
+
|
29
|
+
```
|
30
|
+
$ bin/lex test.rb
|
31
|
+
```
|
32
|
+
|
33
|
+
If you would like to see the full lexed comparison, and not only the output that doesn't match, you can run with `VERBOSE=1`:
|
34
|
+
|
35
|
+
```
|
36
|
+
$ VERBOSE=1 bin/lex test.rb
|
37
|
+
```
|
38
|
+
|
39
|
+
`bin/lex` can also be used with `-e` and then source code, like this:
|
40
|
+
|
41
|
+
```
|
42
|
+
$ bin/lex -e "1 + 2"
|
43
|
+
```
|
44
|
+
|
45
|
+
2. **bin/parse** takes a filepath and outputs YARP's parsed node structure generated from reading the file.
|
46
|
+
|
47
|
+
```
|
48
|
+
$ bin/parse test.rb
|
49
|
+
```
|
50
|
+
|
51
|
+
`bin/parse` can also be used with `-e` and then source code, like this:
|
52
|
+
|
53
|
+
```
|
54
|
+
$ bin/parse -e "1 + 2"
|
55
|
+
```
|