Created
September 20, 2020 04:37
-
-
Save prozacchiwawa/a4d40b722c4f74d51fe26142b12e0d0d to your computer and use it in GitHub Desktop.
WIP designated initialisers diff.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/tdfc2/src/common/construct/initialise.c b/tdfc2/src/common/construct/initialise.c | |
index 4af7a0e0f..9a0c33ed8 100644 | |
--- a/tdfc2/src/common/construct/initialise.c | |
+++ b/tdfc2/src/common/construct/initialise.c | |
@@ -6,9 +6,11 @@ | |
*/ | |
#include <stdio.h> | |
+#include <string.h> | |
#include <shared/check.h> | |
#include <shared/error.h> | |
+#include <shared/xalloc.h> | |
#include <tdf/bitstream.h> | |
@@ -55,6 +57,7 @@ | |
#include <construct/initialise.h> | |
#include <construct/instance.h> | |
#include <construct/inttype.h> | |
+#include <construct/in_order_inits.h> | |
#include <construct/namespace.h> | |
#include <construct/overload.h> | |
#include <construct/statement.h> | |
@@ -1381,9 +1384,14 @@ init_direct(TYPE t, EXP a, ERROR *err) | |
static EXP | |
get_aggr_elem(LIST(EXP) p, unsigned *ptag) | |
{ | |
- EXP a = DEREF_exp(HEAD_list(p)); | |
- if (!IS_NULL_exp(a)) { | |
- if (IS_exp_location(a)) { | |
+ BUFFER buf = { 0 }; | |
+ EXP a; | |
+ | |
+ buf.posn = extend_buffer(&buf, buf.posn); | |
+ | |
+ if (!IS_NULL_list(p)) { | |
+ a = DEREF_exp(HEAD_list(p)); | |
+ if (!IS_NULL_exp(a) && IS_exp_location(a)) { | |
TYPE t; | |
DESTROY_exp_location(destroy, t, crt_loc, a, a); | |
UNUSED(t); | |
@@ -1399,21 +1407,26 @@ get_aggr_elem(LIST(EXP) p, unsigned *ptag) | |
*ptag = tag; | |
} | |
} | |
+ | |
+ free_buffer(&buf); | |
+ | |
return a; | |
} | |
/* | |
- This routine checks the aggregate initialiser expression list pointed | |
- to by r against the type t. The argument start is 1 to indicate the | |
- presence of a open brace immediately preceding r and 2 to indicate | |
- the top-level aggregate. The result is a structured aggregate | |
- initialiser expression for compound types t or a suitably converted | |
- initialiser expression. | |
+ This routine checks the aggregate initialiser expression list pointed | |
+ to by r against the type t. The argument start is 1 to indicate the | |
+ presence of a open brace immediately preceding r and 2 to indicate | |
+ the top-level aggregate. The result is a structured aggregate | |
+ initialiser expression for compound types t or a suitably converted | |
+ initialiser expression. | |
+ | |
+ If limit is BOOL_TRUE, then do not emit padding. | |
*/ | |
static EXP | |
-init_aggr_aux(FieldIterator_t *sf_iter, TYPE t, CV_SPEC cv, LIST(EXP) *r, int start, IDENTIFIER id, | |
- ERROR *err) | |
+init_aggr_aux(FieldIterator_t *sf_iter, TYPE t, CV_SPEC cv, LIST(EXP) *r, int start, LIST(EXP) limit, IDENTIFIER id, | |
+ ERROR *err) | |
{ | |
EXP e; | |
LIST(EXP) p = *r; | |
@@ -1472,13 +1485,13 @@ init_aggr_aux(FieldIterator_t *sf_iter, TYPE t, CV_SPEC cv, LIST(EXP) *r, int st | |
LIST(EXP) q; | |
q = DEREF_list(exp_aggregate_args(e)); | |
field_iterator_push(sf_iter, s, cv); | |
- e = init_aggr_aux(sf_iter, s, cv, &q, 1, id, &serr); | |
+ e = init_aggr_aux(sf_iter, s, cv, &q, 1, id, limit, &serr); | |
field_iterator_pop(sf_iter); | |
p = TAIL_list(p); | |
} else { | |
/* Otherwise read constituents from p */ | |
field_iterator_push(sf_iter, s, cv); | |
- e = init_aggr_aux(sf_iter, s, cv, &p, 0, id, &serr); | |
+ e = init_aggr_aux(sf_iter, s, cv, &p, 0, id, limit, &serr); | |
field_iterator_pop(sf_iter); | |
} | |
@@ -1519,7 +1532,7 @@ init_aggr_aux(FieldIterator_t *sf_iter, TYPE t, CV_SPEC cv, LIST(EXP) *r, int st | |
TYPE s = DEREF_type(type_ref_sub(t)); | |
e = init_ref_rvalue(s, NULL_exp, err); | |
if (IS_NULL_exp(e)) { | |
- e = init_aggr_aux(sf_iter, s, cv, r, start, id, err); | |
+ e = init_aggr_aux(sf_iter, s, cv, r, start, limit, id, err); | |
e = make_temporary(s, e, NULL_exp, 1, err); | |
e = make_ref_init(t, e); | |
} | |
@@ -1597,14 +1610,14 @@ init_aggr_aux(FieldIterator_t *sf_iter, TYPE t, CV_SPEC cv, LIST(EXP) *r, int st | |
LIST(EXP) q; | |
q = DEREF_list(exp_aggregate_args(e)); | |
field_iterator_push(sf_iter, s, cv); | |
- e = init_aggr_aux(sf_iter, s, local_cv, &q, 1, id, | |
+ e = init_aggr_aux(sf_iter, s, local_cv, &q, 1, limit, id, | |
&serr); | |
field_iterator_pop(sf_iter); | |
p = TAIL_list(p); | |
} else { | |
/* Otherwise read constituents from p */ | |
field_iterator_push(sf_iter, s, cv); | |
- e = init_aggr_aux(sf_iter, s, local_cv, &p, 0, id, | |
+ e = init_aggr_aux(sf_iter, s, local_cv, &p, 0, limit, id, | |
&serr); | |
field_iterator_pop(sf_iter); | |
} | |
@@ -1665,7 +1678,7 @@ init_aggr_aux(FieldIterator_t *sf_iter, TYPE t, CV_SPEC cv, LIST(EXP) *r, int st | |
if (et == exp_aggregate_tag) { | |
LIST(EXP)q; | |
q = DEREF_list(exp_aggregate_args(e)); | |
- e = init_aggr_aux(sf_iter, t, cv, &q, 1, id, err); | |
+ e = init_aggr_aux(sf_iter, t, cv, &q, 1, limit, id, err); | |
} else { | |
ERROR ferr = NULL_err; | |
if (start == 1) { | |
@@ -1692,7 +1705,7 @@ token_lab: { | |
goto non_aggregate_lab; | |
} | |
field_iterator_push(sf_iter, s, cv); | |
- e = init_aggr_aux(sf_iter, s, cv, r, start, id, err); | |
+ e = init_aggr_aux(sf_iter, s, cv, r, start, limit, id, err); | |
field_iterator_pop(sf_iter); | |
return e; | |
} | |
@@ -1739,7 +1752,7 @@ non_aggregate_lab: | |
if (et == exp_aggregate_tag) { | |
LIST(EXP) q; | |
q = DEREF_list(exp_aggregate_args(e)); | |
- e = init_aggr_aux(sf_iter, t, cv, &q, 1, id, err); | |
+ e = init_aggr_aux(sf_iter, t, cv, &q, 1, limit, id, err); | |
} else { | |
e = convert_reference(e, REF_ASSIGN); | |
if (tag == type_token_tag && is_zero_exp(e)) { | |
@@ -1773,6 +1786,344 @@ non_aggregate_lab: | |
return e; | |
} | |
+static void | |
+bfprint_designator_exp(BUFFER *name_target, EXP e) { | |
+ unsigned tag = TAG_exp(e); | |
+ | |
+ while (tag == exp_designated_name_tag) { | |
+ IDENTIFIER id = DEREF_id(exp_designated_name_member_name(e)); | |
+ HASHID hid = DEREF_hashid(id_name(id)); | |
+ bfputc(name_target, '.'); | |
+ IGNORE print_hashid(hid, 1, 0, name_target, 0); | |
+ fprintf(stderr, "prefix match build: %s\n", name_target->start); | |
+ e = DEREF_exp(exp_designated_name_member_initialiser(e)); | |
+ tag = TAG_exp(e); | |
+ } | |
+} | |
+ | |
+static unsigned long subscript_compute_idx(EXP e) { | |
+} | |
+ | |
+static void | |
+handle_designated_with_aggregate(FieldIterator_t *sort_iter, InitialisersInOrder_t *designated_inits, | |
+ TYPE t, CV_SPEC cv, LIST(EXP) *r, ERROR *err); | |
+ | |
+static EXP | |
+designated_aggregate_wrap(FieldIterator_t *sort_iter, InitialisersInOrder_t *designated_inits, TYPE s, CV_SPEC cv, EXP inner_exp, ERROR *err) { | |
+ LIST(EXP) elist = NULL_list(EXP); | |
+ int iio_started = designated_inits->iio_cur; | |
+ unsigned tag = TAG_exp(inner_exp); | |
+ | |
+ CONS_list(inner_exp, elist, elist); | |
+ | |
+ if (tag == exp_designated_name_tag) { | |
+ inner_exp = exp_designated_name_member_initialiser(inner_exp); | |
+ handle_designated_with_aggregate(sort_iter, designated_inits, s, cv, &elist, err); | |
+ elist = NULL_list(EXP); | |
+ iio_reduce(designated_inits, iio_started, &elist); | |
+ MAKE_exp_aggregate(s, elist, 0, inner_exp); | |
+ } else if (tag == exp_designated_subscript_tag) { | |
+ inner_exp = exp_designated_subscript_member_initialiser(inner_exp); | |
+ handle_designated_with_aggregate(sort_iter, designated_inits, s, cv, &elist, err); | |
+ elist = NULL_list(EXP); | |
+ iio_reduce(designated_inits, iio_started, &elist); | |
+ MAKE_exp_aggregate(s, elist, 0, inner_exp); | |
+ } else { | |
+ MAKE_exp_aggregate(s, elist, 0, inner_exp); | |
+ } | |
+ | |
+ return inner_exp; | |
+} | |
+ | |
+/* | |
+ This routine checks the aggregate initialiser expression list pointed | |
+ to by r against the type t. The argument start is 1 to indicate the | |
+ presence of a open brace immediately preceding r and 2 to indicate | |
+ the top-level aggregate. The result is a structured aggregate | |
+ initialiser expression for compound types t or a suitably converted | |
+ initialiser expression. | |
+ | |
+ Its interface mirrors init_aggr_aux, but it processes a list containing | |
+ designated initialisers, which need to be collected. | |
+ | |
+ The result at each level is left in a new cell in designated_inits, as | |
+ an exp_aggregate_init if the corresponding item is aggregate, or | |
+ something else matching otherwise. | |
+ | |
+ Leaves behind a properly bracketed expression which can be used with | |
+ init_aggr_aux. | |
+*/ | |
+static void | |
+handle_designated_with_aggregate(FieldIterator_t *sort_iter, InitialisersInOrder_t *designated_inits, | |
+ TYPE t, CV_SPEC cv, LIST(EXP) *r, ERROR *err) | |
+{ | |
+ LIST(EXP) p = *r; | |
+ int i = 0; | |
+ int iio_started = designated_inits->iio_cur; | |
+ unsigned type_tag = TAG_type(t); | |
+ EXP new_aggregate = NULL_exp; | |
+ NAT zero = NULL_nat; | |
+ LIST(EXP) result_args = NULL_list(EXP); | |
+ | |
+ MAKE_nat_small(0, zero); | |
+ | |
+ fprintf(stderr, "handle_designated_with_aggregate type = %u examining %s\n", TAG_type(t), sort_iter->bf->start); | |
+ | |
+ if (type_tag == type_array_tag) { | |
+ TYPE s = DEREF_type(type_array_sub(t)); | |
+ int str = is_char_array(s); | |
+ unsigned long array_bound = get_nat_value(DEREF_nat(type_array_size(t))); | |
+ | |
+ while (!IS_NULL_list(p) && | |
+ (array_bound == EXTENDED_MAX || | |
+ field_iterator_get_index(sort_iter) < array_bound | |
+ ) && | |
+ field_iterator_next(sort_iter)) | |
+ { | |
+ unsigned tag; | |
+ EXP e = get_aggr_elem(p, &tag); | |
+ | |
+ fprintf(stderr, "found exp type %u looking at member %s type %u\n", tag, sort_iter->bf->start, TAG_type(s)); | |
+ | |
+ /* Ordered to mirror init_aggr_aux */ | |
+ if (tag == exp_string_lit_tag && str) { | |
+ e = init_array(t, cv, e, 0, err); | |
+ iio_realloc_copy_in(designated_inits, e); | |
+ } else if (tag == exp_aggregate_tag) { | |
+ LIST(EXP) inner_args = DEREF_list(exp_aggregate_args(e)); | |
+ | |
+ field_iterator_push(sort_iter, s, cv); | |
+ handle_designated_with_aggregate(sort_iter, designated_inits, s, cv, &inner_args, err); | |
+ field_iterator_pop(sort_iter); | |
+ } else if (tag == exp_designated_subscript_tag) { | |
+ EXP inner_exp = exp_designated_subscript_member_initialiser(e); | |
+ unsigned stag = TAG_type(s); | |
+ unsigned long extended = 0; | |
+ unsigned long want_index = subscript_compute_idx(e); | |
+ unsigned long current_idx = field_iterator_get_index(sort_iter); | |
+ | |
+ while (current_idx + extended <= want_index) { | |
+ EXP aggregate = NULL_exp; | |
+ | |
+ if (stag == type_array_tag || stag == type_compound_tag) { | |
+ /* XXX Put in a proper empty 0 */ | |
+ MAKE_exp_aggregate(s, NULL_list(EXP), 0, aggregate); | |
+ } else { | |
+ /* XXX Make a better initialiser, maybe constant 0? */ | |
+ MAKE_exp_int_lit(s, zero, BOOL_FALSE, aggregate); | |
+ } | |
+ | |
+ iio_realloc_copy_in(designated_inits, aggregate); | |
+ extended++; | |
+ } | |
+ | |
+ inner_exp = designated_aggregate_wrap(sort_iter, designated_inits, t, cv, inner_exp, err); | |
+ iio_replace(designated_inits, want_index, inner_exp); | |
+ | |
+ /* XXX Rewind only this level of sort_iter */ | |
+ field_iterator_rewind(sort_iter); | |
+ | |
+ /* Synchronize to the current cursor */ | |
+ while (field_iterator_get_index(sort_iter) < want_index+1) ; | |
+ } else { | |
+ iio_realloc_copy_in(designated_inits, e); | |
+ } | |
+ | |
+ /* Any greed above may have exhausted p */ | |
+ if (!IS_NULL_list(p)) { | |
+ p = TAIL_list(p); | |
+ } | |
+ } | |
+ | |
+ result_args = NULL_list(EXP); | |
+ iio_reduce(designated_inits, iio_started, &result_args); | |
+ MAKE_exp_aggregate(s, result_args, 0, new_aggregate); | |
+ } else if (type_tag == type_compound_tag) { | |
+ int len_before_init = designated_inits->iio_len; | |
+ | |
+ while (!IS_NULL_list(p) && field_iterator_next(sort_iter)) { | |
+ unsigned tag; | |
+ EXP e = get_aggr_elem(p, &tag); | |
+ TYPE s = field_iterator_get_subtype(sort_iter); | |
+ unsigned stag; | |
+ | |
+ stag = TAG_type(s); | |
+ | |
+ fprintf(stderr, "found exp type %u looking at member %s type %u\n", tag, sort_iter->bf->start, stag); | |
+ | |
+ if (tag == exp_aggregate_tag) { | |
+ LIST(EXP) inner_args = DEREF_list(exp_aggregate_args(e)); | |
+ | |
+ field_iterator_push(sort_iter, s, cv); | |
+ handle_designated_with_aggregate(sort_iter, designated_inits, s, cv, &inner_args, err); | |
+ field_iterator_pop(sort_iter); | |
+ } else if (tag == exp_designated_name_tag) { | |
+ /* We've got a designated initialiser in a struct. We'll need to identify the | |
+ index of the branch it refers to, so we'll iterate that type to find it. | |
+ */ | |
+ int search_success; | |
+ BUFFER have_buf = { 0 }; | |
+ BUFFER want_buf = { 0 }; | |
+ FieldIterator_t search_iter = { &have_buf }; | |
+ IDENTIFIER id = DEREF_id(exp_designated_name_member_name(e)); | |
+ HASHID hid = DEREF_hashid(id_name(id)); | |
+ bfputc(&want_buf, '.'); | |
+ | |
+ IGNORE print_hashid(hid, 1, 0, &want_buf, 0); | |
+ field_iterator_init(&search_iter, t, cv); | |
+ | |
+ while ((search_success = field_iterator_next(&search_iter))) { | |
+ if (strcmp(have_buf.start, want_buf.start) == 0) { | |
+ break; | |
+ } | |
+ } | |
+ | |
+ if (search_success) { | |
+ EXP inner_exp; | |
+ unsigned long have_idx = field_iterator_get_index(sort_iter); | |
+ unsigned long want_idx = field_iterator_get_index(&search_iter); | |
+ | |
+ /* We'll reset the iterator here, and leave with an allocated iterator. | |
+ the outer scope will free it. | |
+ */ | |
+ field_iterator_free(&search_iter); | |
+ | |
+ /* Restart the iterator so we can construct the sub-initializers properly */ | |
+ memset(&search_iter, 0, sizeof(search_iter)); | |
+ search_iter.bf = &have_buf; | |
+ field_iterator_init(&search_iter, t, cv); | |
+ | |
+ while (field_iterator_next(&search_iter) && have_idx < want_idx) { | |
+ EXP initialiser = NULL_exp; | |
+ LIST(EXP) agg_list = NULL_list(EXP); | |
+ | |
+ s = field_iterator_get_subtype(sort_iter); | |
+ stag = TAG_type(s); | |
+ | |
+ MAKE_exp_int_lit(s, zero, BOOL_FALSE, initialiser); | |
+ fprintf(stderr, "Creating initialiser for %s\n", search_iter.bf->start); | |
+ inner_exp = designated_aggregate_wrap(sort_iter, designated_inits, s, cv, initialiser, err); | |
+ CONS_list(initialiser, agg_list, agg_list); | |
+ | |
+ if (stag == type_array_tag || stag == type_compound_tag) { | |
+ MAKE_exp_aggregate(s, agg_list, 0, initialiser); | |
+ } else { | |
+ /* XXX ERROR, trying to initialise a non-aggregate with designated member | |
+ name. */ | |
+ initialiser = NULL_exp; | |
+ } | |
+ | |
+ iio_realloc_copy_in(designated_inits, initialiser); | |
+ have_idx++; | |
+ } | |
+ | |
+ iio_seek(designated_inits, want_idx); | |
+ s = field_iterator_get_subtype(sort_iter); | |
+ stag = TAG_type(s); | |
+ | |
+ inner_exp = designated_aggregate_wrap(sort_iter, designated_inits, s, cv, inner_exp, err); | |
+ iio_replace(designated_inits, want_idx, inner_exp); | |
+ | |
+ /* XXX Rewind only this level of sort_iter */ | |
+ field_iterator_rewind(sort_iter); | |
+ | |
+ /* Synchronize to the current cursor */ | |
+ while (field_iterator_get_index(sort_iter) < want_idx+1 && field_iterator_next(sort_iter)) ; | |
+ } else { | |
+ /* XXX Return error */ | |
+ iio_realloc_copy_in(designated_inits, NULL_exp); | |
+ } | |
+ | |
+ field_iterator_free(&search_iter); | |
+ free_buffer(&have_buf); | |
+ free_buffer(&want_buf); | |
+ } else { /* Greedy consume flat */ | |
+ field_iterator_push(sort_iter, s, cv); | |
+ handle_designated_with_aggregate(sort_iter, designated_inits, s, cv, &p, err); | |
+ field_iterator_pop(sort_iter); | |
+ } | |
+ | |
+ /* Above greed may have exhausted p */ | |
+ if (!IS_NULL_list(p)) { | |
+ p = TAIL_list(p); | |
+ } | |
+ } | |
+ | |
+ iio_reduce(designated_inits, iio_started, &result_args); | |
+ MAKE_exp_aggregate(t, result_args, 0, new_aggregate); | |
+ } else { | |
+ CONS_list(DEREF_exp(HEAD_list(p)), result_args, result_args); | |
+ MAKE_exp_aggregate(t, result_args, 0, new_aggregate); | |
+ p = TAIL_list(p); | |
+ } | |
+ | |
+ iio_realloc_copy_in(designated_inits, new_aggregate); | |
+ *r = p; | |
+} | |
+ | |
+void | |
+init_aggr_designated(FieldIterator_t *sf_iter, TYPE t, CV_SPEC cv, LIST(EXP) *r, int start, IDENTIFIER id, | |
+ ERROR *err) | |
+{ | |
+ BUFFER sort_iter_buf = { 0 }; | |
+ BUFFER init_buf = { 0 }; | |
+ FieldIterator_t sort_iter = { &sort_iter_buf }; | |
+ InitialisersInOrder_t designated_inits = { 0 }; | |
+ int partition_start = 0; | |
+ int swap_at = 0; | |
+ int i; | |
+ EXP init_expr; | |
+ LIST(EXP) p = *r; | |
+ LIST(EXP) elist = NULL_list(EXP); | |
+ | |
+ fprintf(stderr, "init_aggr_designated\n"); | |
+ | |
+ sort_iter_buf.posn = extend_buffer(&sort_iter_buf, sort_iter_buf.posn); | |
+ init_buf.posn = extend_buffer(&init_buf, init_buf.posn); | |
+ | |
+ field_iterator_init(&sort_iter, t, cv_none); | |
+ | |
+ /* Do initial copy */ | |
+ handle_designated_with_aggregate(&sort_iter, &designated_inits, t, cv, &p, err); | |
+ iio_reduce(&designated_inits, 0, &elist); | |
+ | |
+ iio_free(&designated_inits); | |
+ field_iterator_free(&sort_iter); | |
+ free_buffer(&sort_iter_buf); | |
+ free_buffer(&init_buf); | |
+ | |
+ *r = elist; | |
+} | |
+ | |
+/* | |
+ Determine whether we've got designated initialisers to deal with as | |
+ these won't allow strictly linear handling. | |
+ | |
+ This traverses the list. | |
+*/ | |
+ | |
+static int | |
+detect_designated_initialiser(LIST(EXP) p) { | |
+ fprintf(stderr, "Detecting desginated\n"); | |
+ while (!IS_NULL_list(p)) { | |
+ unsigned tag; | |
+ EXP a = get_aggr_elem(p, &tag); | |
+ | |
+ if (tag == exp_designated_name_tag) { | |
+ fprintf(stderr, "Found a designated initialiser\n"); | |
+ return BOOL_TRUE; | |
+ } else if (tag == exp_aggregate_tag) { | |
+ LIST(EXP) args = DEREF_list(exp_aggregate_args(a)); | |
+ if (detect_designated_initialiser(args)) { | |
+ fprintf(stderr, "Found a designated initialiser\n"); | |
+ return BOOL_TRUE; | |
+ } | |
+ } | |
+ p = TAIL_list(p); | |
+ } | |
+ fprintf(stderr, "No designated initialiser\n"); | |
+ return BOOL_FALSE; | |
+} | |
/* | |
This is the top-level routine for analysing the aggregate initialiser | |
@@ -1796,7 +2147,18 @@ init_aggregate(TYPE t, EXP e, IDENTIFIER id, ERROR *err) | |
bad_crt_loc++; | |
loc = crt_loc; | |
field_iterator_init(&sf_iter, t, cv_none); | |
- e = init_aggr_aux(&sf_iter, t, cv_none, &args, 2, id, err); | |
+ /* If We've got one or more designated initialisers, and the feature is reqested, | |
+ * then do designated initialisation. */ | |
+ if (detect_designated_initialiser(args)) { | |
+ fprintf(stderr, "init_aggr_designated - begin\n"); | |
+ init_aggr_designated(&sf_iter, t, cv_none, &args, 2, id, err); | |
+ fprintf(stderr, "init_aggr_designated - end\n"); | |
+ } | |
+ | |
+ fprintf(stderr, "init_aggr_aux - begin\n"); | |
+ e = init_aggr_aux(&sf_iter, t, cv_none, &args, 2, NULL_list(EXP), id, err); | |
+ fprintf(stderr, "init_aggr_aux - end\n"); | |
+ | |
field_iterator_free(&sf_iter); | |
free_buffer(&field_buffer); | |
crt_loc = loc; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment