JSON parse_list(…)
Learning goals
You will learn or practice how to:
- Parse strings
- Apply test-driven development (TDD).
- Use
union
andenum
types. - Write programs that use dynamic memory (
malloc(…)
) - Debug code that uses dynamic memory (
malloc(…)
) - Practice writing programs with linked lists.
- Write programs using more complex data structures (beyond the cannonical linked list and BST).
Overview
This is part 3 of a 4-part sequence in which you will create a decoder for the JSON data format.
JSON is a file format for exchanging hierarchical data between programs, often written in different programming languages and/or on different computers. In web programming, this allows a server application written in any language (e.g., Python or even C) to pass complex structures of information to the user's browser, which then renders it.
A JSON string may represent a number, a string, or a list. In this context, when we say “list”, we mean a linked list. Here are some examples:
type | json | data |
---|---|---|
number | 10 | 10 |
string | "ten" | "ten" |
list | [2, 6, 4] |
Parts of this assignment
Originally, this was one assignment. Over time, we have split it into pieces to help you manage your time and efforts. (The assignment has also evolved, as all assignments do.)
HW11 |
|
HW12 |
|
HW13 |
|
HW14 |
|
Linked lists
As in C, whitespace is ignored (except within a string). Thus, the following are all equivalent.
type | json | data |
---|---|---|
list | [2, 6, 4] | |
list | [2, 6, "four"] | |
list | [2, [3, "+", 3], "four"] |
Getting Started on HW13
- Use
264get HW13
to fetch the starter code.you@eceprog ~/ $
cd 264
you@eceprog ~/264/ $
264get hw13
- Copy your miniunit.h, log_macros.h, and Makefile from any previous assignment.
you@eceprog ~/264/ $
mv HW██/miniunit.h HW██/log_macros.h HW██/Makefile -v -t hw13/
you@eceprog ~/264/ $
cd hw13
you@eceprog ~/264/hw13 $
- Implement
parse_int(…)
.- Create a trivial test (e.g., 0).
- Implement just enough of
parse_int(…)
so that it passes your trivial test. At this point, it should fail most other tests. - Submit.
- Add another simple trivial test (e.g., 9).
- Implement just enough to pass your two tests so far.
- Submit.
- Add another test (e.g., 15).
- Implement just enough to pass your three tests so far.
- Submit.
- Add another test (e.g., -15).
- … and so on, until
parse_int(…)
is finished.
- Test your
parse_int(…)
completely, using miniunit.h. Getparse_int(…)
completely finished and tested to perfection—including error handling—before you do anything else. Seriously… do not do anything else until this is done. - Submit.
- Implement
parse_element(…)
just enough to support integers.- Add a test for a plain integer.
Be sure to call it from
main(…)
.static int _test_parse_element_int_plain() { mu_start(); //──────────────────── Element element; // will be initialized by parse_element(…) char const* input = "1"; char const* pos = input; bool is_success = parse_element(&element, &pos); mu_check(is_success); mu_check(element.as_int == 1); mu_check(element.type == ELEMENT_INT); mu_check(pos == input + 1); // pos should now refer to the byte just after the '1' at input. mu_check(*pos == '\0'); // That byte should be the null terminator. //──────────────────── mu_end(); }
- Add just enough code to
parse_element(…)
to make this test pass. Expect to add about 2−4 lines of code insideparse_element(…)
. - Test.
- Submit.
- Extend the test function above to test for negative numbers.
// negative number input = "-2"; pos = input; is_success = parse_element(&element, &pos); mu_check(is_success); mu_check(element.as_int == -2); mu_check(element.type == ELEMENT_INT); mu_check(pos == input + 2); // pos should now refer to the byte just after the '2'. mu_check(*pos == '\0'); // That byte should be the null terminator.
- Test.
- Submit.
- Add a test for a integer with leading whitespace.
Be sure to call it from
main(…)
.static int _test_parse_element_with_leading_whitespace() { mu_start(); //──────────────────── Element element; // will be initialized by parse_element(…) char const* input = " 1"; char const* pos = input; bool is_success = parse_element(&element, &pos); mu_check(is_success); mu_check(element.as_int == 1); mu_check(element.type == ELEMENT_INT); mu_check(pos == input + 3); // pos should now refer to the byte just after the '1' at input. mu_check(*pos == '\0'); // That byte should be the null terminator. //──────────────────── mu_end(); }
- Add just enough code to
parse_element(…)
to make this test pass. Expect to add about 1−3 lines of code insideparse_element(…)
. - Test.
- Submit.
- Add a test function for some more cases involving integers. Here is a start. This is not adequate. You need to extend this with your own test cases.
static int _test_parse_element_int_oddballs() { mu_start(); //──────────────────── Element element; // will be initialized by parse_element(…) char const* input = " 4 A"; char const* pos = input; bool is_success = parse_element(&element, &pos); mu_check(is_success); mu_check(element.as_int == 4); mu_check(element.type == ELEMENT_INT); mu_check(pos == input + 2); // pos should now refer to the byte just after the '1' at input. mu_check(*pos == ' '); // That byte contains a space. // TODO: Add more tests here based on your ideas for tests. //──────────────────── mu_end(); }
- You may or may not not need to add any code to make these tests pass.
- Test.
- Submit.
- Add a test function for strings that contain integers but are not valid JSON because they are
preceded by junk characters (not whitespace).
static int _test_parse_element_invalid() { mu_start(); //──────────────────── Element element; // will be initialized by parse_element(…) char const* input = "--4"; char const* pos = input; bool is_success = parse_element(&element, &pos); mu_check(! is_success); mu_check(pos == input + 1); // pos should now refer to second '-' since that is the // character where we learn that this is not a valid JSON // expression for an int. mu_check(*pos == '-'); // That byte contains a '-'. // TODO: Add more tests here based on your ideas for tests. //──────────────────── mu_end(); }
- You may or may not not need to add any code to make these tests pass.
- Test.
- Submit.
- Add a test for a plain integer.
Be sure to call it from
- Implement
parse_string(…)
.- Create a test function for a valid JSON expression representing an empty string.
static int _test_parse_string_valid_empty() { mu_start(); //────────────────────────────────────────────────────── char* result; char const* input = "\"\""; char const* pos = input; bool is_success = parse_string(&result, &pos); mu_check(is_success); // because the input is valid mu_check_strings_equal("", result); mu_check(pos == input + 2); mu_check(*pos == '\0'); free(result); //────────────────────────────────────────────────────── mu_end(); }
- Implement just enough of
parse_string(…)
to make this test pass. - Test.
- Submit.
- Create a test function for a valid JSON expression representing a string of size one.
static int _test_parse_string_valid_one_char() { mu_start(); //────────────────────────────────────────────────────── char* result; char const* input = "\"A\""; char const* pos = input; bool is_success = parse_string(&result, &pos); mu_check(is_success); // because the input is valid mu_check_strings_equal("A", result); mu_check(pos == input + 3); mu_check(*pos == '\0'); free(result); //────────────────────────────────────────────────────── mu_end(); }
- Implement just enough of
parse_string(…)
to make this test pass. - Test.
- Submit.
- Create a test function for a valid JSON expression representing a string containing multiple characters.
static int _test_parse_string_valid_multiple_chars() { mu_start(); //────────────────────────────────────────────────────── char* result; char const* input = "\"ABC\""; char const* pos = input; bool is_success = parse_string(&result, &pos); mu_check(is_success); // because the input is valid mu_check_strings_equal("ABC", result); mu_check(pos == input + 5); mu_check(*pos == '\0'); free(result); //────────────────────────────────────────────────────── mu_end(); }
- Implement just enough of
parse_string(…)
to make this test pass. - Test.
- Submit.
- Add test function(s) to check strings that contain double quotation marks but are not valid JSON. Here is a start. You will need to add more of your own.
static int _test_parse_string_invalid() { mu_start(); //────────────────────────────────────────────────────── char* result; char const* input = "\"A"; char const* pos = input; bool is_success = parse_string(&result, &pos); mu_check(! is_success); // because the input is valid mu_check(pos == input + 2); mu_check(*pos == '\0'); // We do not call free(…) if the string was invalid. input = "\"A\nB\""; pos = input; is_success = parse_string(&result, &pos); mu_check(! is_success); // because the input is valid mu_check(pos == input + 2); mu_check(*pos == '\n'); // TODO: Add more tests of your own. //────────────────────────────────────────────────────── mu_end(); }
- Create a test function for a valid JSON expression representing an empty string.
-
Extend
parse_element(…)
to support strings. You will need to implementfree_element(…)
in the same step.- Add a test for a plain string containing multiple characters.
Be sure to call it from
main(…)
.static int _test_parse_element_string() { mu_start(); //──────────────────── Element element; // will be initialized by parse_element(…) char const* input = "\"ABC\""; char const* pos = input; bool is_success = parse_element(&element, &pos); mu_check(is_success); mu_check_strings_equal("ABC", element.as_string); mu_check(element.type == ELEMENT_STRING); mu_check(pos == input + 5); // pos should now refer to the byte just after the second double quote. mu_check(*pos == '\0'); // That byte should be the null terminator. free_element(element); //──────────────────── mu_end(); }
- Add just enough code to
parse_element(…)
to make this test pass. Expect to add about 4 lines of code insideparse_element(…)
and 1−3 lines insidefree_element(…)
. - Test.
- Submit.
- Add additional tests as needed to ensure that
parse_element(…)
works for JSON expressions representing strings and integers. - Test.
- Submit.
- Add a test for a plain string containing multiple characters.
Be sure to call it from
- Implement
print_element(…)
so that it supports integers and strings.- Add a test for
Element
objects that contain an integer. This will not use Miniunit. Since this is simple, you an use visual inspection (just look).static void _test_print_element() { Element element; // will be initialized by parse_element(…) char const* input = "123"; bool is_success = parse_element(&element, &input); printf("Testing parse_element(&element, \"123\")\n"); printf(" - Expected: 123\n"); printf(" - Actual: "); print_element(element); fputc('\n', stdout); free_element(element); }
- Add just enough code to
print_element(…)
to make this test pass. Expect to add about 1―3 lines of code insideprint_element(…)
. - Test.
- Submit.
- Add a test for
Element
objects that contain an string. Again, this will not use Miniunit.static int _test_print_element() { mu_start(); //────────────────────────────────────────────────────── // This uses diff testing to check print_element(…). Element element; // will be initialized by parse_element(…) char const* input = "\"ABC\""; bool is_success = parse_element(&element, &input); printf("Testing parse_element(&element, \"\\\"ABC\\\"\")\n"); printf(" - Expected: \"ABC\"\n"); printf(" - Actual: "); print_element(element); fputc('\n', stdout); free_element(element); //────────────────────────────────────────────────────── mu_end(); }
- Add just enough code to
print_element(…)
to make this test pass. Expect to add about 3―5 lines of code insideprint_element(…)
. - Test.
- Submit.
- Add a test for
- Implement
parse_list(…)
.- Create a trivial test (e.g., []).
- Implement just enough of
parse_list(…)
so that it passes your trivial test (functionality only). It should fail all other tests with linked lists. - Extend
free_element(…)
so that yourparse_list(…)
tests work without any memory leaks (e.g., passes Valgrind). - Add another simple test (e.g., [0]).
- Implement just enough to pass your two list tests so far.
- Add another simple test (e.g., ["a"]).
- Implement just enough to pass your three list tests so far.
- Add a test with multiple integers as list items (e.g., [1, 2]).
- Implement just enough to pass your tests so far.
- Add a test with multiple strings as list items (e.g., ["A", "B"]).
- You shouldn't need to add any code for this, but make sure it works 100% before you proceed.
- Add a test with an empty list as a list item (e.g., [[]]).
- Implement just enough to pass your tests so far.
- Add a test with a non-empty list as a list item (e.g., [[1]]).
- Implement just enough to pass your tests so far.
- Add a few more tests with gradually increasing complexity (e.g., [[1, 2]], [1, [2, 3], 4], [[1, 2], [3, 4], []]).
- Test as needed. Get things perfect—including memory (Valgrind) and test coverage (gcov)—and submit at every stage.
- Test error cases. Make sure
parse_int(…)
,parse_list(…)
, andparse_element(…)
return false for incorrect input. - Test that there are no memory leaks, even for incorrect input.
How much work is this?
You must be disciplined in your development appraoch. If you try to build this all at once, success is extremely unlikely.
HW11:
Instructor's solution for
parse_int(…)
only is 18 sloc
(14 sloc in the body of parse_int(…)
).
HW12:
Instructor's solution for
parse_int(…)
,
parse_string(…)
, and
parse_element(…)
only
is 75 sloc.
HW14:
Instructor's solution for
parse_int(…)
,
parse_string(…)
,
parse_list(…)
, and
parse_element(…)
is 127 sloc.
SLOC means “source lines of code” and does not lines that are blank or contain only comments. The figures above do not count test_json.c. Also, those figures are based on a tight—but defensible—solution that meets code quality standards and uses no methods or language features we have not yet covered.
Requirements
- Your submission must contain each of the following files, as specified:
file contents json.c functions parse int(int✶ a value, char const✶✶ a pos)
→ return type: boolSet*a_value
to whatever integer value is found at*a_pos
.*a_pos
is initially the address of the first character of the integer literal in the input string.*a_value
is the (already allocated) location where the parsed int should be stored.- Return
true
if a properly formed integer literal was found at*a_pos
.*a_pos
should refer to the next character in the input string, i.e., after the last digit of the integer literal.- Ex:
parse_int(…)
should returntrue
for 9, -99, and 123AAA.
- Ex:
- Return
false
if an integer literal was not found.*a_pos
should refer to the unexpected character that indicated a problem.- Ex:
parse_int(…)
should returnfalse
for A, AAA123, -A, -A9, and -.
- Ex:
- ⚠ Calling
parse_int(…)
should not result in any calls tomalloc(…)
. - You do not need to parse hexadecimal, octal, scientific
notation, floating point values, or anything other than
integers in decimal notation (positive or negative).
You may assume any
integer is within the range of an int on our platform
(i.e., ≥
INT_MIN
and ≤INT_MAX
). - Whenever
parse_int(…)
returnsfalse
,*a_value
should not be modified. parse_int(…)
should ignore any trailing characters that come after the last digit in the number.
Examples
parse_int(…)
should returntrue
for any of these:
9 -99 768336YAK 768336 EMUparse_int(…)
should returnfalse
for any of these:
A AAA123 -A -A9 -parse string(char✶✶ a string, char const✶✶ a pos)
→ return type: boolSet*a_string
to a copy of the string literal at*a_pos
.- Caller is responsible for freeing the memory.
- A string literal must be surrounded by double quotation marks, and
may not contain a newline. In addition, we make two simplifications:
- Strings may not contain double quotation marks.
- Backslash is not special. Do not parse escape codes (i.e., "\▒") in the input.
- Calling
parse_string(…)
should result in exactly one call tomalloc(…)
. - Return
true
if a properly formed string literal was found. *a_pos should be set to the next character in the input string, i.e., after the ending double quotation mark. - Return
false
if a string literal was not found. *a_pos should refer to the unexpected character that indicated a problem (e.g., newline or null terminator in the input). - Whenever
parse_string(…)
returnsfalse
, do not modify*a_string
, and no heap memory should be allocated. parse_string(…)
should ignore any trailing characters that come after the closing double quotation mark.
Examples
parse_string(…)
should returntrue
for any of these:
"antfox" "emubee\" "tompup\z" "ratkoi"YAK "doecod" YAKparse_string(…)
should returnfalse
for any of these:
" henjay" "ram
dog" 768336"eelfly" 768336 "catbug"parse list(Node✶✶ a head, char const✶✶ a pos)
→ return type: boolSet*a_head
to the head of a linked list ofElement
objects.- Caller is responsible for freeing the memory if
parse_list(…)
returnstrue
. - Linked list consists of a
'['
, followed by 0 or more JSON-encoded elements (integers, strings, or lists) separated by commas, and finally a']'
. See the examples above. (There will be no HBB on this definition, unless there is something truly wrong and/or grossly unclear.) There may be any number/amount of whitespace characters (' '
,'\n'
, or'\t'
), before/after any of the elements. - Return
true
if a properly formed list was found. *a_pos should be set to the next character in the input string, after the list that was just parsed.- Ex:
parse_list(…)
should returntrue
for [], [1,2], [1,"A"], [1,[2]], and [1]A.
- Ex:
- Return
false
if a list was not found (i.e., syntax error in the input string). *a_pos should refer to the unexpected character that indicated a problem.- Ex:
parse_list(…)
should returnfalse
for A[], [1,,2], [[, 1,2, and ,2]. - Whenever parse_list returns
False
, you must ensure that all heap memory allocated as a result of that call toparse_list(…)
is freed. In other words, testingparse_list(…)
with an invalid JSON list representation (e.g.,["one",[1,2],3,,]
) should not result in a memory leak.
- Ex:
parse_list(…)
should ignore any trailing characters that come after the closing square bracket.
parse element(Element✶ a element, char const✶✶ a pos)
→ return type: bool- First, eat any whitespace at
*a_pos
.- “Eat whitespace” just means to skip over any whitespace characters (i.e., increment
*a_pos
untilisspace(**a_pos)==false
).
- “Eat whitespace” just means to skip over any whitespace characters (i.e., increment
- Next, decide what kind of element this is.
- If it's a digit (
isdigit(**a_pos)
) or hyphen ('-'
), set the element'stype
toELEMENT_INT
and callparse int(&(a element -> as int), a pos)
. - If it's a string (
**a_pos=='"'
), then set the element'stype
toELEMENT_STRING
and callparse string(&(a element -> as string), a pos)
. - If it's a list (
**a_pos == '['
), then set the element'stype
toELEMENT_LIST
and call:parse list(&(a element -> as list), a pos)
.
- If it's a digit (
- Return whatever was returned by
parse_int(…)
parse_string(…)
orparse_list(…)
.- If none of those functions was called—i.e., if the next character was neither digit,
'-'
,'"'
, nor'['
—then returnfalse
.
- If none of those functions was called—i.e., if the next character was neither digit,
- Do not modify
*a_pos
directly inparse_element(…)
, except for eating whitespace.*a_pos
can—and should—be modified inparse_int(…)
andparse_string(…)
and andparse_list(…)
- Caller is responsible for freeing memory by calling
free_element(…)
wheneverparse_element(…)
returnstrue
. - Whenever
parse_element(…)
returnsfalse
, do not modify*element
. Any heap memory allocated prior to discovery of the error should be freed.
parse json(char const✶ json)
→ return type: ParseResultGiven a JSON string, parse it and return aParseResult
object.- If parsing succeeds—i.e., the string is a valid JSON representation of
any JSON object, such as an integer, string, or list—then the
.is_success
field of theParseResult
object should betrue
and the.element
field (of the anonymous union) should contain theElement
object, as returned byparse_element(…)
.-
Calling
parse_json("768336");
should return aParseResult
object equivalent to this compound literal:
(ParseResult) { .is_success = true, .element = (Element) { .type = ELEMENT_INT, .as_int = 768336 } }
-
Calling
- If parsing fails, then the
.is_success
field of theParseResult
object should befalse
and the.error_idx
field (of the anonymous union) should contain the index into json of the first character that indicated a parse failure.-
Example: Calling
parse_json("768336X");
should return aParseResult
object equivalent to this compound literal:
(ParseResult) { .is_success = false, .error_idx = 6 }
-
Example: Calling
parse_json("X768336");
should return aParseResult
object equivalent to this compound literal:
(ParseResult) { .is_success = false, .error_idx = 0 }
-
Example: Calling
- Ignore leading or trailing whitespace.
-
Example: Parsing
768336,
768336 , or
768336 should return an equivalent result
with
.is_success == true
and.element.as_int == 768336
.
-
Example: Parsing
768336,
768336 , or
768336 should return an equivalent result
with
-
Hint:
parse_json(…)
should not need to do anything special for each type (i.e., int, string, list); if it works for one, it should work with the others with no modification.parse_json(…)
is just a wrapper forparse_element(…)
that gives you a more usable, understandable result. parse_json(…)
should NOT ignore any non-whitespace trailing characters that come after the JSON object.-
Example: Parsing
X768336,
768336X,
768336 X, or
X 768336 X should result in a
ParseResult
object with.is_success == false
and.error_idx
equal to the index of the firstX
character (i.e., 0, 6, 7, or 1, respectively). - ⚠ This is different from
parse_int(…)
,parse_string(…)
,parse_list(…)
, andparse_element(…)
.
print element(Element element)
→ return type: voidGiven anElement
object, print it in JSON notation.- Spacing is up to you, as long as it is valid JSON.
- If element is an integer, print it using
printf(…)
. - If element is a string, then print it (with double-quotes) using
printf(…)
. - If element is a list, print a
'['
. Then print each element in the list usingprint_element(…)
(recursively), separated by commas. Finally, print']'
.
free element(Element element)
→ return type: voidFree the contents of theElement
, as needed.- If it contains a string, free the string.
- If it contains a linked list, free the list, including all elements.
- ⚠ Do not attempt to free the
Element
object itself.free_element(element)
only frees dynamic memory that element refers to.
json.h header ParseResultAdd a definition for a struct type calledParseResult
as needed to work withparse_json(…)
. Leave everything else in the header file as is.test_json.c functions main(int argc, char✶ argv[])
→ return type: intTest your all of the above functions using yourminiunit.h.
.- This should consist primarily of calls to
mu_run(_test_▒▒▒)
. - 100% code coverage is required.
- Your main(…) must return EXIT_SUCCESS.
-
parse_int(…)
,parse_string(…)
,parse_list(…)
, andparse_element(…)
should ignore any trailing characters in the input string, as long as it starts with a well-formed JSON element.- Acceptable: 123AAA, "12"AAA, "12",[,
- You only need to support the specific features of JSON that are explicitly required in this assignment description. You do not need to support unicode (e.g., "萬國碼", "يونيكود", "യൂണികോഡ്"), objects/dictionaries (e.g., {"a":1, "b":2}), backslash escapes (e.g., "\n"), embedded quotes (e.g., "He said, \"Roar!\""), floating point numbers (e.g., 3.1415), non-decimal notations (e.g., 0xdeadbeef, 0600), null, false)
- Do not modify json.h except as explicitly directed.
- There may be no memory faults (e.g., leaks, invalid read/write, etc.), even when
parse_▒▒▒(…)
return false. -
The following external header files, functions, and symbols are
allowed.
header functions/symbols allowed in… stdbool.h bool
,true
,false
json.c
,test_json.c
stdio.h printf
,fprintf
,fputs
,stdout
,fflush
json.c
,test_json.c
assert.h assert
json.c
,test_json.c
ctype.h isdigit
,isspace
json.c
,test_json.c
stdlib.h EXIT_SUCCESS
,abs
,malloc
,free
,size_t
json.c
,test_json.c
string.h strncpy
,strchr
,strlen
,strcmp
json.c
,test_json.c
limits.h INT_MIN
,INT_MAX
json.c
,test_json.c
miniunit.h anything
test_json.c
log_macros.h anything
json.c
,test_json.c
- Submissions must meet the code quality standards and the course policies on homework and academic integrity.
Submit
To submit HW13 from within your hw13 directory,
type
264submit HW13 json.c json.h test_json.c miniunit.h log_macros.h Makefile
If your code does not depend on miniunit.h or log_macros.h, those may be omitted. Makefile will not be checked, but including it may help in case we need to do any troubleshooting.
Pre-tester ●
The pre-tester for HW13 has been released and is ready to use.
Q&A
How can I structure my tests?
Here is a start. This assumes you have written your
parse_json(…)
function andParseResult
type.// Okay to copy/adapt, but ONLY IF YOU UNDERSTAND THIS CODE COMPLETELY. // ⚠ Do not copy blindly. #include <stdio.h> #include <stdlib.h> #include <string.h> #include "json.h" #include "miniunit.h" int _test_int_valid_0() { mu_start(); //──────────────────── ParseResult result = parse_json("0"); mu_check(result.is_success); if(result.is_success) { mu_check(result.element.type == ELEMENT_INT); mu_check(result.element.as_int == 0); free_element(result.element); // should do nothing } //──────────────────── mu_end(); } int _test_int_valid_0() { mu_start(); //──────────────────── ParseResult result = parse_json("0"); mu_check(result.is_success); if(result.is_success) { mu_check(result.element.type == ELEMENT_INT); mu_check(result.element.as_int == 0); free_element(result.element); // should do nothing } //──────────────────── mu_end(); } int _test_string_valid_abc() { mu_start(); //──────────────────── result = parse_json("\"abc\""); mu_check(result.is_success); if(result.is_success) { mu_check(result.element.type == ELEMENT_STRING); mu_check(strcmp(result.element.as_string, "abc") == 0); mu_check(strlen(result.element.as_string) == 3); free_element(result.element); } //──────────────────── mu_end(); } static int _test_list_of_ints_valid_1_2() { mu_start(); //──────────────────── ParseResult result = parse_json("[1, 2]"); mu_check(result.is_success); if(result.is_success) { mu_check(result.element.type == ELEMENT_LIST); mu_check(result.element.as_list != NULL); mu_check(result.element.as_list -> element.as_int == 1); mu_check(result.element.as_list -> element.type == ELEMENT_INT); mu_check(result.element.as_list -> next != NULL); mu_check(result.element.as_list -> next -> element.type == ELEMENT_INT); mu_check(result.element.as_list -> next -> element.as_int == 2); free_element(result.element); } //──────────────────── mu_end(); } int main(int argc, char* argv[]) { mu_run(_test_int_valid_0); mu_run(_test_string_valid_abc); mu_run(_test_list_of_ints_valid_1_2); return EXIT_SUCCESS; }
⚠ If you do not understand this code, do not use it.The code above covers all parts of this assignment. If you choose to use it, you will likely need to select only the parts relevant to the part you are doing right now. In any case, that is not an adequate test on its own. You will need more. This is just to get you started.
What if I don't have parse_json(…) done yet?
Here is a simpler version without
parse_json(…)
// OK TO COPY / ADAPT this snippet---but ONLY if you understand it completely. // ⚠ Do not copy blindly. // // This test is nowhere near adequate on its own. It is provided to illustrate how to // use helper functions to streamline your test code. #include <stdio.h> #include <stdlib.h> #include "json.h" #include "miniunit.h" static int _test_parse_int_valid() { mu_start(); //────────────────────────────────────────────────────── int result; // will be initialized in parse_int(…) char* input = "0"; char const* pos = input; bool is_success = parse_int(&result, &pos); mu_check(is_success); // because the input is valid mu_check(pos == input + 1); mu_check(result == 0); //────────────────────────────────────────────────────── mu_end(); } static int _test_parse_int_invalid() { mu_start(); //────────────────────────────────────────────────────── int result; // will be initialized in parse_int(…) char* input = "A"; char const* pos = input; bool is_success = parse_int(&result, &pos); mu_check(!is_success); // because the input is valid mu_check(pos == input); // failure should be at the first character in the input //────────────────────────────────────────────────────── mu_end(); } int main(int argc, char* argv[]) { mu_run(_test_parse_int_valid); mu_run(_test_parse_int_invalid); return EXIT_SUCCESS; }
What should be the value of
*a_pos
afterparse_▒▒▒(…)
returns?_____________________________________________ # EXAMPLE #1 INPUT: 123 BEFORE we call parse_int(…): 123 ↑ *a_pos RETURN value from parse_int(…): true After parse_int(…) returns: 123 ↑ *a_pos refers to null terminator just after the integer literal. element.type == ELEMENT_INT element.as_int == 123 _____________________________________________ # EXAMPLE #2 INPUT: 123ABC BEFORE we call parse_int(…): 123ABC ↑ *a_pos RETURN value from parse_int(…): true After parse_int(…) returns: 123ABC ↑ *a_pos refers to the non-digit character after the integer literal. element.type == ELEMENT_INT element.as_int == 123 _____________________________________________ # EXAMPLE #3 INPUT: -A1 BEFORE we call parse_int(…): -A1 ↑ *a_pos RETURN value from parse_int(…): false After parse_int(…) returns: -A1 ↑ *a_pos refers first character that informed us this cannot be an integer literal. element.type == (don't care) element.as_int == (don't care)
What does the output of
print_element(…)
look like?It's just the inverse operation toparse_element(…)
.parse_element(…)
takes JSON as input.print_element(…)
prints JSON as output.If you were to parse the output ofprint_element(…)
withparse_element(…)
you should get an equivalent object.If you parse a JSON string and then print it again, you should get an equivalent string.If you're looking for concrete examples, just look at any example of input toparse_element(…)
(except for the trailing characters). There are several at the top of this assignment description page.The specification says we don't have to handle escape sequences, but then it mentions escape sequences. Do we parse escape sequences or don't we? How do we handle backslash?
We use C escape codes to make C string literals containing certain characters (e.g., double-quote, newline, etc.) in our C code.You don't have to parse JSON escape codes. Unless you are doing the escape sequence bonus, just treat a backslash like any other character.Here's an example to illustrate the distinction:#include <stdlib.h> #include <assert.h> #include <string.h> #include <stdio.h> #include "json.h" int main(int argc, char* argv[]) { Element element; // 'element' will be initialized inside parse_element(…) // C escape codes used to create a C string literal containing double quotes char* json_input = "\"A\""; // same as: {'\"', 'A', '\"'} char const* pos = json_input; assert(strlen(json_input) == 3); parse_element(&element, &pos); printf(">>>|%s|<<<\n", element.as_string); // Output: // >>>|A|<<< // JSON escape codes json_input = "\"A\\nB\""; // same as: {'\"', 'A', '\\', 'n', 'B', '\"'} assert(strlen(json_input) == 5); pos = json_input; parse_element(&element, &pos); printf(">>>|%s|<<<\n", element.as_string); // Output: // >>>|A\nB|<<< // Output: (with escape code bonus) // >>>|A // B|<<< // Note: strlen(…) does not count the null terminator return EXIT_SUCCESS; }
Should the double quotes be stored in memory?
No. The double quotes are part of the JSON syntax.This is just like how in C, when you define a string like this:… the double quotes are not stored in memory.char s[] = "abc";
Updates
10/30/2022 |
|