Question
· Dec 14, 2023

Can I extract a JS function body in cos?

I got a problem to resolve, to sum it up I need to extract a function body from a JS file with cos.
I already got the JS code in a string and the function name but I'm kinda stuck as to how to get my function body, I already tried a few things (regEx, counter bracket) but there are comment and strings that block them from functioning. Is there a javascript parser or a good way to do it except by creating a javascript parser?

Discussion (8)3
Log in or sign up to continue

@Stephane Devin 
You can use %SyntaxColor to parse JavaScript. Here's a very simple example that reads in a JS file, parses it, and returns a JSON representation of the semantic tokens:

ClassMethod JSTokens() As %Boolean
{
    #; Reading from a file, writing to a temporary stream
    Set syn = ##class(%SyntaxColor).%New(), in = ##class(%Stream.FileCharacter).%New(), out = ##class(%Stream.TmpCharacter).%New()
    #; Need the "K" flag to get JSON output
    Do in.LinkToFile("/Users/bsaviano/Desktop/test.js"), syn.Color(in,out,"JS","K")
    #; Get a %DynamicArray from the stream
    Set tokens = ##class(%DynamicArray).%FromJSON(out)
    #; Process JSON ...
    #; JSON is of the format:
    #; { 
    #;     // The position of the token within the line
    #;     p: number;
    #;     // The length of the token
    #;     c: number; 
    #;     // Language number, see %SyntaxColor::Languages()
    #;     l: number; 
    #;     // Attribute number, see %SyntaxColor::Attributes()
    #;     s: number; 
    #; }[][]
    #; Where there is one array per line of the source document
}

I suggest you study the class reference for %Library.SyntaxColor since it's not that easy to use.

after some testing I found out that multiple token have the same attribute number
As I haven't found documentation about wich token is wich attribute number I put it here.

what i found out for wich attribute number equal to wich JS token :
1 : new line or blank space / tabulation
2 : no occurence in my file
3 : no occurence in my file
4 : ( ) [ ] { } ; , : .
5 : " '
6 : comment //
7 : number
8 : no occurence in my file
9: no occurence in my file 
10 : / single slash (probably to delimit regEx)
11 : ^ + | $ (in regEx)
12 : \s (maybe escaped char in regEx)
13 : g (maybe the mode of a regEx)
14 : variable, function name (call and def)
15 :  =
16 : function and var keywords
and I stopped here.

@Stephane Devin 
Here's a pretty-printed version of the attributes list for JavaScript:

Attribute 0: Error
Attribute 1: White Space
Attribute 2: _Tab
Attribute 3: Label
Attribute 4: Delimiter
Attribute 5: String
Attribute 6: Comment
Attribute 7: Decimal integer
Attribute 8: Hexadecimal integer
Attribute 9: Floating point number
Attribute 10: Regexp delimiter
Attribute 11: Regexp body
Attribute 12: Regexp escape sequence
Attribute 13: Regexp flags
Attribute 14: Identifier
Attribute 15: Operator
Attribute 16: Definition keyword
Attribute 17: Statement keyword
Attribute 18: Literal keyword
Attribute 19: Expression keyword
Attribute 20: Future keyword
Attribute 21: CSP extension
Attribute 22: JSON property name

I have done it 😁 it need some polish but here is the code to accomplish it using the %SyntaxColor library:

Method ExtractJsFunctionBody(jsCodePath As %String, fnName As %String)
{
  
    #; Reading from a file, writing to a temporary stream
   set syn = ##class(%SyntaxColor).%New(), in = ##class(%Stream.FileCharacter).%New(), out = ##class(%Stream.TmpCharacter).%New()
    // set js content in content var    
   set content = in.Read($$$MaxStringLength)
   do InStream.Rewind()
   #; Need the "K" flag to get JSON output
   do in.LinkToFile(jsCodePath), syn.Color(in,out,"JS","K")
   #; Get a %DynamicArray from the stream
   set lines = ##class(%DynamicArray).%FromJSON(out)
   // get list by line of jsCode
   set lineList = $listfromstring(content, $char(10))

   // get pos and line of function def begining 
   set iterLines = lines.%GetIterator()
   while iterLines.%GetNext(.linenumber , .line, .type) {

      set value = line.%Get(1)
      if ((value.%Get("s") = 16) &&  (value.%Get("c") = 8)){  
         set value = line.%Get(3)
         if (value.%Get("s") = 14 ){
            set tempFnName = $extract($list(lineList, linenumber + 1),value.%Get("p") + 1, value.%Get("p") + value.%Get("c"))
            if (tempFnName = fnName ){
               set functionLineBegin = linenumber + 1
               quit
            }
         }
      }
   }
   
   // get pos of function def ending
   set functionLineEnd = ..braketCounter(iterLines, lineList)
   
   //extract functiun Body
   set functionBody = $listtostring($list(lineList,functionLineBegin,functionLineEnd),$char(10))
   write !!!, functionBody
}

/// Count braket and return the line of the corresponding ending one take an iterrator at the line of the begining of the search and a list of the lines
Method braketCounter(iterLines As %Iterator.AbstractIterator, lineList As %DynamicArray) As %Integer
{
   set countBraket = 1
   while iterLines.%GetNext(.linenumber , .line, .type) { // we keep the same iterator to continue from where we where
      set iterTokens = line.%GetIterator()
      while iterTokens.%GetNext(.key , .value, .type){
         set token = $extract($list(lineList, linenumber + 1),value.%Get("p") + 1, value.%Get("p") + value.%Get("c") )
         if (value.%Get("s") = 4) {
            if (  token = "{"){
               set countBraket = countBraket + 1}
            elseif ( token = "}"){
               set countBraket = countBraket - 1}
            if (countBraket = 0){
               return linenumber 
            }
         }
         
      }
   }
   return "error"
}