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.

Usage

[rexx] elident [options] file

Options

-h, --help Display help and exit
-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                            */
/* 20252111         Add Executor support, move to /bin                        */
/* 20252118         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                              */
/*                                                                            */
/******************************************************************************/

  Signal On Syntax

  package =  .context~package

  myName  =   package~name
  Parse Caseless Value FileSpec( "Name", myName ) With myName".rex"
  myHelp  = ChangeStr(                                         -
   "myName",                                                   -
   "https://rexx.epbcn.com/rexx-parser/doc/utilities/myName/", -
    myName)
  Parse Source . how .
  If how == "COMMAND", .SysCArgs \== .Nil
    Then args = .SysCArgs
    Else args = ArgArray(Arg(1))

  executor = 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 "-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))

  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    currentLine ||= element~source
    End
    element = element~next
  End

  Exit 0

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

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
-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"