@@ -445,13 +445,13 @@ std::string lex_number(const char *&c, const std::string &filename, const Locati
445445 case ' 7' :
446446 case ' 8' :
447447 case ' 9' : state = AFTER_EXP_DIGIT; break ;
448-
448+
449449 case ' _' : state = AFTER_EXP_UNDERSCORE; goto skip_char;
450450
451451 default : goto end;
452452 }
453453 break ;
454-
454+
455455 case AFTER_EXP_UNDERSCORE:
456456 switch (*c) {
457457 // The only valid transition from _ is to a digit.
@@ -496,17 +496,26 @@ static int whitespace_check(const char *a, const char *b)
496496 return i;
497497}
498498
499- /*
500- static void add_whitespace(Fodder &fodder, const char *s, size_t n)
501- {
502- std::string ws(s, n);
503- if (fodder.size() == 0 || fodder.back().kind != FodderElement::WHITESPACE) {
504- fodder.emplace_back(FodderElement::WHITESPACE, ws);
499+ static void describe_whitespace (std::stringstream& msg, const std::string& ws) {
500+ int spaces = 0 ;
501+ int tabs = 0 ;
502+ for (char c : ws) {
503+ if (c == ' ' )
504+ spaces++;
505+ else if (c == ' \t ' )
506+ tabs++;
507+ }
508+ if (spaces > 0 && tabs > 0 ) {
509+ msg << spaces << (spaces == 1 ? " space" : " spaces" ) << " and " << tabs
510+ << (tabs == 1 ? " tab" : " tabs" );
511+ } else if (spaces > 0 ) {
512+ msg << spaces << (spaces == 1 ? " space" : " spaces" );
513+ } else if (tabs > 0 ) {
514+ msg << tabs << (tabs == 1 ? " tab" : " tabs" );
505515 } else {
506- fodder.back().data += ws ;
516+ msg << " no indentation " ;
507517 }
508518}
509- */
510519
511520Tokens jsonnet_lex (const std::string &filename, const char *input)
512521{
@@ -840,17 +849,48 @@ Tokens jsonnet_lex(const std::string &filename, const char *input)
840849 // Examine next line
841850 ws_chars = whitespace_check (first_line, c);
842851 if (ws_chars == 0 ) {
843- // End of text block
844- // Skip over any whitespace
852+ // End of text block (or indentation error).
853+ // Count actual whitespace on this line.
854+ int actual_ws = 0 ;
855+ while (c[actual_ws] == ' ' ||
856+ c[actual_ws] == ' \t ' ) {
857+ actual_ws++;
858+ }
859+
860+ // Check if this is the terminator |||
861+ bool is_terminator = (
862+ c[actual_ws] == ' |' &&
863+ c[actual_ws + 1 ] == ' |' &&
864+ c[actual_ws + 2 ] == ' |' );
865+
866+ if (!is_terminator) {
867+ // Not a terminator - check if it's an
868+ // indentation issue.
869+ if (actual_ws > 0 ) {
870+ // Has whitespace but doesn't match expected
871+ // indentation.
872+ std::stringstream msg;
873+ msg << " text block indentation mismatch: "
874+ " expected at least " ;
875+ describe_whitespace (msg, string_block_indent);
876+ msg << " , found " ;
877+ describe_whitespace (msg, std::string (c, actual_ws));
878+ throw StaticError (filename, begin, msg.str ());
879+ } else {
880+ // No whitespace and no ||| - missing
881+ // terminator.
882+ auto msg =
883+ " text block not terminated with |||" ;
884+ throw StaticError (filename, begin, msg);
885+ }
886+ }
887+
888+ // Valid termination - skip over any whitespace.
845889 while (*c == ' ' || *c == ' \t ' ) {
846890 string_block_term_indent += *c;
847891 ++c;
848892 }
849- // Expect |||
850- if (!(*c == ' |' && *(c + 1 ) == ' |' && *(c + 2 ) == ' |' )) {
851- auto msg = " text block not terminated with |||" ;
852- throw StaticError (filename, begin, msg);
853- }
893+ // Skip the |||
854894 c += 3 ; // Leave after the last |
855895 data = block.str ();
856896 kind = Token::STRING_BLOCK;
0 commit comments