#!/usr/bin/env bash # definitions.sh — catch duplicate top-level definitions, and name the files. # # El has no namespacing. `import` is textual inlining, so two modules defining # the same name emit two C functions into one translation unit. cc catches it, # but reports the generated helpers (__el_body_f, __env_f, __thunk_f) before the # user's own function, so the first three errors name symbols nobody wrote. # # Naming the FILES needed provenance threaded end to end: tokens had no line # numbers at all, so no diagnostic in El could name a place. Now a token is # (kind, value, line), FnDef carries its line, and resolve_imports publishes # which line range of the combined source came from which file. # # LIMIT: a nested import returns one string, so a definition inside a # transitively imported file is attributed to the direct import. set -uo pipefail REL="${1:?usage: definitions.sh }" [ -f "$REL" ] || exit 0 # line in the COMBINED source -> "file:line-within-that-file". Reporting the # combined line against a filename would point at a line that file does not # have, which is worse than reporting no line at all. locate() { awk -v L="$1" '$2=="spans" && $3<=L && $4>=L {printf "%s:%d", $1, L-$3+1; found=1; exit} END{ if(!found) printf "" }' "$REL" } V=0 while read -r name; do lines=$(grep -E "^$name calls defines_at:" "$REL" | sed 's/.*defines_at://' | sort -un) n=$(echo "$lines" | wc -l | tr -d ' ') [ "$n" -gt 1 ] || continue printf "duplicate definition: '%s' is defined %s times — El has no namespacing, so imported modules share one global scope\n" "$name" "$n" for l in $lines; do loc=$(locate "$l") [ -n "$loc" ] && printf " %s\n" "$loc" || printf " combined line %s\n" "$l" done V=$((V+1)) done < <(grep ' calls defines_at:' "$REL" | awk '{print $1}' | sort -u) [ "$V" -eq 0 ] && echo "definitions: clean" exit "$V"