Elident


Elident


Elident ("ELement IDENTity") is a self-consistency utility. It checks that a program is identical to the concatenation of the values of all its parsed elements.

Compound variables are checked part by part, and standard comments, doc comments, and resource data are handled specially.

If the check succeeds, Elident exits silently with exit code 0. If a difference is found, Elident displays the mismatching source and parsed lines and exits with exit code 1.

Usage

[rexx] elident [options] file

When called without arguments, display help information and exit.

Options

-h, --help Display help and exit
-e, --experimental   Enable Experimental features (also -exp)
-it, --itrace Print internal traceback on error
-u, --tutor, --unicode   Enable TUTOR-flavored Unicode
-xtr, --executor Enable Executor support

Program source

#!/usr/bin/env rexx
/******************************************************************************/
/*                                                                            */
/* elident.rex - Check that a program is equal to its Element API parsing     */
/* ======================================================================     */
/*                                                                            */
/* This program is part of the Rexx Parser package                            */
/* [See https://rexx.epbcn.com/rexx-parser/]                                  */
/*                                                                            */
/* Copyright (c) 2024-2026 Josep Maria Blasco <josep.maria.blasco@epbcn.com>  */
/*                                                                            */
/* License: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0)  */
/*                                                                            */
/* Version history:                                                           */
/*                                                                            */
/* Date     Version Details                                                   */
/* -------- ------- --------------------------------------------------------- */
/* 20241206    0.1  First public release                                      */
/* 20241208    0.1a c/CLASSIC_COMMENT/STANDARD_COMMENT/                       */
/* 20250328    0.2  Main dir is now rexx-parser instead of rexx[.]parser      */
/* 20251110    0.3a Change the name to elident.rex                            */
/* 20251211         Add Executor support, move to /bin                        */
/* 20251218         Add TUTOR support                                         */
/* 20251221    0.4a Add --itrace option, improve error messages               */
/* 20251226         Send error messages to .error, not .output                */
/* 20251227         Use .SysCArgs when available                              */
/* 20260103         Use compound variable parts                               */
/* 20260307    0.5  Implement experimental option                             */
/* 20260314         Use InitCLI() from CLISupport.cls                         */
/*                                                                            */
/******************************************************************************/

  Signal On Syntax

  CLIhelper    = InitCLI()
  myName       = CLIhelper~name
  myHelp       = CLIhelper~help
  args         = CLIhelper~args

  executor     = 0
  experimental = 0
  unicode      = 0
  itrace       = 0

ProcessOptions:
  If args~items == 0 Then Signal Help

  option = args[1]
  args~delete(1)

  If option[1] == "-" Then Do
    Select Case Lower(option)
      When "-h", "--help"                 Then Signal Help
      When "--executor", "-xtr"           Then executor     = 1
      When "-e", "-exp", "--experimental" Then experimental = 1
      When "-it", "--itrace"              Then itrace       = 1
      When "-u", "--tutor", "--unicode"   Then unicode      = 1
      Otherwise Call Error "Invalid option '"option"'."
    End
    Call ProcessOptions
  End

  If args~items > 0 Then Call Error "Invalid argument '"args[1]"'."

  file = option

  fullPath = .context~package~findProgram(file)

  If fullPath == .Nil Then Call Error "File '"file"' does not exist."

  -- Read the whole file into an array
  chunk = CharIn(fullPath,1,Chars(fullPath))
  Call Stream fullPath,"c","close"
  source = chunk~makeArray
  -- Makearray has a funny definition that ignores a possible
  -- last empty line.
  If Right(chunk,1) = "0a"X Then source~append("")

  options = .Array~new
  If executor     Then options~append(("EXECUTOR",     1))
  If unicode      Then options~append(("UNICODE",      1))
  If experimental Then options~append(("EXPERIMENTAL", 1))

  parser = .Rexx.Parser~new( file, source, options )

  currentLineNo = 1
  currentLine   = ""

  element = parser~firstElement -- Same as parser~package~prolog~body~begin
  Do Counter elements Until element == .Nil
    If element~from \== element~to Then Do
      category = element~category
      elementLine  = element~from~word(1)
      If      elementLine > currentLineNo          Then Call ChangeLine
      If      category == .EL.STANDARD_COMMENT     Then Call StandardComment
      Else If category == .EL.DOC_COMMENT          Then Call StandardComment
      Else If category == .EL.DOC_COMMENT_MARKDOWN Then Call StandardComment
      Else If category == .EL.RESOURCE_DATA        Then Call ResourceData
      Else If category == .EL.EXPOSED_COMPOUND_VARIABLE | -
              category == .EL.COMPOUND_VARIABLE    Then Call CompoundVariable
      Else    currentLine ||= element~source
    End
    element = element~next
  End

  Exit 0

Help:
  Say .Resources[Help]~makeString        -
    ~caselessChangeStr("myName", myName) -
    ~caselessChangeStr("myHelp", myHelp)
  Exit 1

CompoundVariable:
  Do part Over element~parts
    currentLine ||= part~source
  End
  Return

StandardComment:
  lastLine = element~to~word(1)
  start = element~from~word(2)
  end   = element~  to~word(2)
  If elementLine == lastLine Then Do
    currentLine ||= source[currentLineNo][ start, end-start ]
    Return
  End
  elementLine += 1
  currentLine ||= SubStr( source[currentLineNo], start )
  Call ChangeLine
  currentLineNo = lastLine
  currentLine   = source[currentLineNo][1, end - 1]
Return

ResourceData:
  lastLine = element~to~word(1)
  currentLineNo = lastLine + 1
  currentLine   = ""
Return

ChangeLine:
  Do While elementLine > currentLineNo
    If source[currentLineNo] \== currentLine Then Do
      Say( "Difference found in line number" currentLineNo":" )
      Say "Source line is '"source[currentLineNo]"',"
      Say "Parsed line is '"currentLine"'."
      Exit 1
    End
    currentLineNo += 1
    currentLine    = ""
  End
Return

--------------------------------------------------------------------------------

Error:
 .Error~Say(Arg(1))
  Exit 1

--------------------------------------------------------------------------------
-- Standard Rexx Parser error handler                                         --
--------------------------------------------------------------------------------

Syntax:
  co = condition("O")
  If co~code \== 98.900 Then Do
   .Error~Say( "Error" co~code "in" co~program", line" co~position":" )
    Raise Propagate
  End
  Exit ErrorHandler( file, source, co, itrace)

::Resource HELP
myname -- Checks that the Parser' stream of elements is identical to a FILE.

Usage: myname [OPTION]... [FILE]

If the only option is -h or --help, or if no arguments are present,
then display this help and exit.

Options:

-it, --itrace             Print internal trace on error
-u, --tutor, --unicode    Enable TUTOR-flavored Unicode
-e, -exp, --experimental  Enable Experimental features
-xtr, --executor          Activate support for Executor language extensions

The 'myname' program is part of the Rexx Parser package,
see https://rexx.epbcn.com/rexx-parser/. It is distributed under
the Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0).

Copyright (c) 2024-2026 Josep Maria Blasco <josep.maria.blasco@epbcn.com>.

See myHelp for details.
::END

::Requires "Rexx.Parser.cls"
::Requires "BaseClassesAndRoutines.cls"
::Requires "ErrorHandler.cls"
::Requires "CLISupport.cls"