+ }
+ return ret;
+}
+
+static bool is_empty(const char *line)
+{
+ return strspn(line, " \r\t") == strlen(line);
+}
+
+static bool continues(const char *line)
+{
+ /* Technically, any odd number of these. But who cares? */
+ return strends(line, "\\");
+}
+
+static bool parse_hash_if(struct pp_conditions *cond, const char **line)
+{
+ bool brackets, defined;
+
+ cond->inverse = get_token(line, "!");
+ defined = get_token(line, "defined");
+ brackets = get_token(line, "(");
+ cond->symbol = get_symbol_token(cond, line);
+ if (!cond->symbol)
+ return false;
+ if (brackets && !get_token(line, ")"))
+ return false;
+ if (!defined)
+ cond->type = PP_COND_IF;
+
+ /* FIXME: We just chain them, ignoring operators. */
+ if (get_token(line, "||") || get_token(line, "&&")) {
+ struct pp_conditions *sub = tal(cond, struct pp_conditions);
+
+ sub->parent = cond->parent;
+ sub->type = PP_COND_IFDEF;
+ if (parse_hash_if(sub, line))
+ cond->parent = sub;
+ }
+
+ return true;
+}
+
+/* FIXME: Get serious! */
+static struct pp_conditions *analyze_directive(struct ccan_file *f,
+ const char *line,
+ struct pp_conditions *parent)
+{
+ struct pp_conditions *cond = tal(f, struct pp_conditions);
+ bool unused;
+
+ line = remove_comments(f, line, false, &unused);
+
+ cond->parent = parent;
+ cond->type = PP_COND_IFDEF;
+
+ if (!get_token(&line, "#"))
+ abort();
+
+ if (get_token(&line, "if")) {
+ if (!parse_hash_if(cond, &line))
+ goto unknown;
+ } else if (get_token(&line, "elif")) {
+ /* Malformed? */
+ if (!parent)
+ return NULL;
+ cond->parent = parent->parent;
+ /* FIXME: Not quite true. This implies !parent, but we don't
+ * do multiple conditionals yet. */
+ if (!parse_hash_if(cond, &line))
+ goto unknown;
+ } else if (get_token(&line, "ifdef")) {
+ bool brackets;
+ cond->inverse = false;
+ brackets = get_token(&line, "(");
+ cond->symbol = get_symbol_token(cond, &line);
+ if (!cond->symbol)
+ goto unknown;
+ if (brackets && !get_token(&line, ")"))
+ goto unknown;
+ } else if (get_token(&line, "ifndef")) {
+ bool brackets;
+ cond->inverse = true;
+ brackets = get_token(&line, "(");
+ cond->symbol = get_symbol_token(cond, &line);
+ if (!cond->symbol)
+ goto unknown;
+ if (brackets && !get_token(&line, ")"))
+ goto unknown;
+ } else if (get_token(&line, "else")) {
+ /* Malformed? */
+ if (!parent)
+ return NULL;
+
+ *cond = *parent;
+ cond->inverse = !cond->inverse;
+ return cond;
+ } else if (get_token(&line, "endif")) {
+ tal_free(cond);
+ /* Malformed? */
+ if (!parent)
+ return NULL;
+ /* Back up one! */
+ return parent->parent;
+ } else {
+ /* Not a conditional. */
+ tal_free(cond);
+ return parent;
+ }
+
+ if (!is_empty(line))
+ goto unknown;
+ return cond;
+
+unknown:
+ cond->type = PP_COND_UNKNOWN;
+ return cond;
+}
+
+/* This parser is rough, but OK if code is reasonably neat. */
+struct line_info *get_ccan_line_info(struct ccan_file *f)
+{
+ bool continued = false, in_comment = false;
+ struct pp_conditions *cond = NULL;
+ unsigned int i;
+
+ if (f->line_info)
+ return f->line_info;
+
+ get_ccan_file_lines(f);
+ f->line_info = tal_arr(f->lines, struct line_info,
+ tal_count(f->lines)-1);
+
+ for (i = 0; f->lines[i]; continued = continues(f->lines[i++])) {
+ char *p;
+ bool still_doc_line;
+
+ /* Current conditions apply to this line. */
+ f->line_info[i].cond = cond;
+ f->line_info[i].continued = continued;
+
+ if (continued) {
+ /* Same as last line. */
+ f->line_info[i].type = f->line_info[i-1].type;
+ /* Update in_comment. */
+ remove_comments(f, f->lines[i], in_comment, &in_comment);