# JOE Syntax-Highlighting Description # for # JOE Syntax-Highlighting Descriptions # # Author: Charles J. Tabony # Date: 2007-2-13 # # This is a highlighting description for files like this one. # # When CHECKING is defined, it is very aggressive about error checking. The # idea is that anywhere the highlighted file contains a syntax error, at least # one visible character should be highlighted as Bad. While that feature is # useful for finding syntax errors, it is annoying when editing a file, since # nearly everything is an error until you finish typing it. # # In order to not annoy people by default, but keep the option of strictly # checking syntax, I predicated the stricter checking on the CHECKING parameter. # By default, things that are incomplete are generally not marked as errors. # Only things that appear to be actual mistakes are highlighted as Bad. To # enable the stricter checking, one can highlight the file with the jsf_check # syntax. jsf_check.jsf simply calls the entire jsf.jsf file with CHECKING # defined. # # The idea is for authors of a jsf file to edit their file, highlight it with # jsf_check, and then look for any red characters. That way they can check for # syntax errors before testing the changes. ##################### # Color Definitions # ##################### =Idle =Comment =Conditional +Precond +Preproc =Parameter +Ident =Keyword =Color +Type =ColorRef =State +Ident =Subr +Ident =Constant =Number +Constant =String +Constant =StringEscape +Escape +String =Bad ################## # Initial States # ################## # This is a dummy state that simply jumps to comment_or_bad. It is here so that # when this file calls itself with the STRINGS parameter defined, comment_or_bad # will effectively be the initial state. comment_or_bad should be the initial # state because strings and istrings options can only be used as the last option # of a transition. .ifdef STRINGS :strings_initial Idle * comment_or_bad noeat .endif # Each new line (that is not considered bad from the beginning) begins in the # idle state. The first non-whitespace character determines what the rest of # the line should contain. Following a strings or istrings option, only strings # and comments are allowed until the word "done" denotes the end of the list. :idle Idle * bad noeat " \t\n" idle .ifdef STRINGS .else "-" sync_lines_first "." conditional_first mark recolor=-1 "=" color_definition_first ":" state_first "*&%" special_character recolor=-1 .endif "\"" string recolor=-1 .ifdef STRINGS "\i" special_word mark recolor=-1 buffer .endif "#" comment recolor=-1 ############## # Sync Lines # ############## # Following a '-' should be either the number of sync lines or nothing (meaning # unlimited). Nothing else other than a comment should appear on the same line. .ifdef STRINGS # A sync lines directive should not appear between "[i]strings" and "done". .else # If we see a non-digit or a '0', then we have seen the entire sync lines # directive. The only thing that may appear on the rest of the line is a # comment. Otherwise there may be more digits in the number. :sync_lines_first Number * comment_or_bad noeat "0" comment_or_bad "1-9" sync_lines # Highlight the remainder of the number. :sync_lines Number * comment_or_bad noeat "0-9" sync_lines .endif ########################## # Conditional Directives # ########################## # Following a '.' should be a conditional directive. .ifdef STRINGS # A conditional directive should not appear between "[i]strings" and "done". .else # Start buffering the conditional directive. :conditional_first Conditional * conditional noeat buffer # Recognize the set of conditional directives. :conditional Idle * conditional_unknown noeat strings "ifdef" ifdef_color "else" conditional_color "endif" conditional_color "subr" subr_color "end" conditional_color done "\c" conditional # We encountered what looks like a conditional directive but is unrecognized as # such. :conditional_unknown Idle .ifdef CHECKING * bad_line recolormark noeat .else * comment_or_bad noeat .endif # We saw a conditional directive that does not take an argument. Nothing else # other than a comment should appear on the same line. :conditional_color Conditional * comment_or_bad noeat # We saw a ".ifdef" which must be followed by a parameter. :ifdef_color Conditional * need_parameter noeat # We loop over whitespace until we see the first character of the parameter. :need_parameter Idle * bad noeat " \t" need_parameter "\i" parameter recolor=-1 # Now we highlight the remainder of the parameter. :parameter Parameter * comment_or_bad noeat "\c" parameter # The following three states are identical to the previous three except the # color. :subr_color Conditional * need_subr noeat :need_subr Idle * bad noeat " \t" need_subr "\i" subr recolor=-1 :subr Subr * comment_or_bad noeat "\c" subr .endif #################### # Color Definition # #################### # Following an '=' should be a color definition. .ifdef STRINGS # Color definitions should not appear between "[i]strings" and "done". .else # A color name must have at least one character. :color_definition_first Color * color_definition " \t#\n" bad noeat # Highlight any remaining characters until we see whitespace, a comment, or a # newline. :color_definition Color * color_definition " \t#\n" colors_ws noeat # The color name may be followed by zero or more standard colors or attributes, # ending in a comment or newline. :colors_ws Idle * color_bad recolor=-1 " \t" colors_ws "+" color_ref recolor=-1 "#\n" comment noeat :color_ref ColorRef * colors_ws noeat "\c" color_ref # We have encountered something that is not recognized as a standard color or # attribute. Continue to highlight characters as Bad until we see whitespace, a # comment, or a newline. :color_bad Bad * color_bad " \t#\n" colors_ws noeat .endif ######### # State # ######### # Following a ':' should be a state definition. .ifdef STRINGS # New states should not appear between "[i]strings" and "done". .else # A state name must begin with an alpha character or an underscore. :state_first State * bad noeat "\i" state # Subsequent characters in a state name must be alpha-numeric or underscores. :state State * bad noeat "\c" state " \t" need_state_color recolor=-1 # A state must have a color. :need_state_color Idle * state_color recolor=-1 " \t" need_state_color "#\n" bad noeat # Highlight any remaining characters until we see whitespace, a comment, or a # newline. :state_color Color * state_color " \t" context_ws recolor=-1 "#\n" comment_or_bad noeat # Following the state color, there might be one or more contexts. Loop over # whitespace until we find something else. :context_ws Idle * comment_or_bad noeat " \t" context_ws "\i" context mark recolor=-1 buffer # Here we recognize the possible contexts. :context Idle * context_unknown noeat strings "comment" context_color "string" context_color done "\c" context # We encountered what looks like a context but is unrecognized as such. :context_unknown Idle .ifdef CHECKING * context_bad recolormark noeat .else * context_ws noeat .endif # We encountered a valid context. :context_color Keyword * context_ws noeat # We saw something that is not a valid context name with checking enabled. # Continue to highlight it as Bad until we see whitespace or a comment. :context_bad Bad * context_bad " \t#\n" context_ws noeat .endif ############## # Transition # ############## # A state transition starts with a '*', an '&', or a string. .ifdef STRINGS # Transitions must start with a string between "[i]strings" and "done". .else # We saw either a '*' or an '&'. Now we need the next state. :special_character Keyword * need_next_state noeat .endif # We are in a string. Continue until we see the close quote or a newline. # Highlight escaped characters within the string differently. They start with a # '\'. :string String string * string "\\" escape recolor=-1 "\"" need_next_state .ifdef CHECKING "\n" bad .else "\n" bad noeat .endif # Highlight an escaped character within a string. :escape StringEscape string * string # Loop over whitespace until we see the first character of the next state. :need_next_state Idle * bad noeat " \t" need_next_state "\i" next_state recolor=-1 # Now we highlight the remainder of the next state. :next_state State * bad noeat "\c" next_state " \t" options_ws "#\n" comment noeat # Following the next state should be zero or more options. Loop over whitespace # until we find an option, comment, or newline. :options_ws Idle * option_bad recolor=-1 " \t" options_ws "\i" option mark recolor=-1 buffer "#\n" comment noeat # Here we recognize the possible options. The strings and istrings options # cannot be used between "[i]strings" and "done". Since conditional directives # cannot be used between "[i]strings" and "done" either, the list must be # duplicated, once without and once with the strings and istrings options. :option Idle .ifdef STRINGS * option_unknown recolormark noeat strings "noeat" option_color "recolor" recolor_color "mark" option_color "markend" option_color "recolormark" option_color "buffer" option_color "save_c" option_color "save_s" option_color "hold" option_color "call" call_color "return" option_color "reset" option_color done .else * option_unknown recolormark noeat strings "noeat" option_color "recolor" recolor_color "mark" option_color "markend" option_color "recolormark" option_color "buffer" option_color "save_c" option_color "save_s" option_color "strings" strings_color "istrings" strings_color "hold" option_color "call" call_color "return" option_color "reset" option_color done .endif "\c" option # We encountered what looks like an option but is unrecognized as such. :option_unknown Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif # We have encountered an option that does not take an argument. Highlight it # and continue to look for more options. :option_color Keyword * options_ws noeat .ifdef STRINGS # The strings and istrings options cannot be used between "[i]strings" and # "done". .else # The strings and istrings options are followed by a list of transitions. # Rather than duplicate all of the states that highlight transitions, we call # this entire file as a subroutine and use the STRINGS parameter to disable # everything else and enable the done keyword. We return to the comment_or_bad # state since we will return after seeing the done keyword, and nothing but a # comment should follow the done keyword. :strings_color Keyword * comment_or_bad noeat call=jsf(STRINGS) .endif # Highlight the recolor option. :recolor_color Keyword * recolor_equal noeat # The recolor option must be followed by an '='. Loop over whitespace until we # find one. :recolor_equal Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" recolor_equal "=" recolor_minus mark # The recolor option takes an integer argument, and that integer must be # negative. Thus the '=' must be followed by a minus sign. Loop over # whitespace until we find one. :recolor_minus Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" recolor_minus "-" recolor_amount_first mark recolor=-1 # The first digit of the argument to recolor must be non-zero. :recolor_amount_first Number .ifdef CHECKING * option_bad recolormark noeat .else * options_ws recolormark noeat "0" option_bad recolormark noeat .endif "1-9" recolor_amount # Keep highlighting digits until we see something else. :recolor_amount Number * option_bad recolormark recolor=-1 "0-9" recolor_amount " \t#\n" options_ws noeat # Highlight the call option. :call_color Keyword * call_equal noeat # The call option must be followed by an '='. Loop over whitespace until we # find one. :call_equal Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" call_equal "=" call_file_or_dot mark # The first part of the argument to the call option is the name of the file # containing the subroutine or a '.', implying the current file. Loop over # whitespace until we see one of those two things. :call_file_or_dot Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" call_file_or_dot "\i" call_file mark recolor=-1 "." call_dot mark # Highlight the remainder of the file name. The file name can be followed by a # '.', which must then be followed by the name of a subroutine, or by a list of # parameters in parentheses. The '.', if present, cannot have whitespace on # either side. :call_file Subr .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif "\c" call_file "." call_dot mark recolor=-1 " \t(" call_open_paren noeat # We saw a '.'. The next character must start the name of a subroutine. :call_dot Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif "(" call_dot_bad recolormark noeat "\i" call_subr mark recolor=-1 # We have seen a dot followed by an open parenthesis. A dot must be followed by # a subroutine name. Highlight the dot as Bad. :call_dot_bad Bad * call_open_paren noeat # Highlight the remainder of the subroutine name. Following the subroutine name # must be a list of parameters in parentheses, possibly preceded by whitespace. :call_subr Subr .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif "\c" call_subr " \t(" call_open_paren noeat # Loop over whitespace until we find the open parenthesis. :call_open_paren Idle .ifdef CHECKING * option_bad recolormark noeat .else * options_ws noeat .endif " \t" call_open_paren "(" call_parameters_ws # The list of parameters is delimited by whitespace. Loop over whitespace until # we find either the beginning of a parameter or a close parenthesis. We should # not see a comment or newline since the list should be terminated by a close # parenthesis. :call_parameters_ws Idle * call_parameter_bad recolor=-1 " \t" call_parameters_ws "-" call_parameter_undef "\i" call_parameter recolor=-1 ")" options_ws "#\n" bad noeat # We saw a "-". The next character should start the parameter being undefined. :call_parameter_undef Parameter * call_parameters_ws noeat "\i" call_parameter recolor=-2 # Highlight the remainder of the parameter. :call_parameter Parameter * call_parameters_ws noeat "\c" call_parameter # We saw something that is not a valid parameter name. Continue to highlight it # as Bad until we see whitespace. :call_parameter_bad Bad * call_parameter_bad ") \t#\n" call_parameters_ws noeat # We saw something that is not a valid option name. Continue to highlight it as # Bad until we see whitespace or a comment. :option_bad Bad * option_bad " \t#\n" options_ws noeat ######## # Done # ######## .ifdef STRINGS # The special word, "done", can only be used after a strings or istrings option. # Recognize the done keyword. :special_word Idle * bad_line recolormark noeat strings "done" done_color done "\c" special_word # Highlight the done keyword and return to highlighting things normally, since # the list of strings has been terminated. :done_color Keyword * comment_or_bad return noeat .endif ################## # Comment or Bad # ################## # We have seen everything that should appear on the current line except an # optional comment. Loop over whitespace until we find a comment or newline. :comment_or_bad Idle * bad noeat " \t" comment_or_bad "#\n" comment noeat ########### # Comment # ########### # Continue to highlight the comment until the end of the line. :comment Comment comment * comment "BFHNTX" comment noeat call=comment_todo.comment_todo() "\n" idle ####### # Bad # ####### .ifdef CHECKING # We have encountered incorrect syntax. Loop over whitespace until we see the # first visible character. Highlight that character and the rest of the line as # Bad. :bad Bad * bad_line " \t\n" bad .else # When not performing strict checking, don't go searching for the next visible # character to highlight as Bad. Simply highlight the rest of the line as Bad, # even if it is invisible. :bad Bad * bad_line noeat .endif # Continue to highlight everything as Bad until the end of the line. :bad_line Bad * bad_line "\n" idle