| Index | Manual | Download | Source | Macros | Embedded word Case ins.
Sitemap | Links | Forum | Tests | Projects

# src.htm
Class
SRC
Procedures
SrcAssemble
SrcCreate
SrcDestroy
SrcFetchLine
SrcPosition

Src represents one source file which is assembled by €ASM.
As there can be only one source being assembled at the moment, the object Src is defined statically in [.bss] segment.

Src has its own memory pool.

The input source file name and its suboperation are provided by Ea.SrcFile and Ea.SubPtr.

SrcAssemble is invoked by EaAssemble with the source file name specified in `Ea.SrcFile` . Path of source filename is also used to locate the local option file euroasm.ini and to locate the listing file, which will have the same filename as source but appended with extension .lst.

    src PROGRAM FORMAT=COFF,MODEL=FLAT,WIDTH=32
        INCLUDEHEAD euroasm.htm, \  Interface (structures, symbols and macros) of other modules used in this source.
                    chunk.htm,ctx.htm,dict.htm,ea.htm,eaopt.htm,exp.htm,lst.htm,msg.htm,pgm.htm, \
                    pgmopt.htm,reloc.htm,sss.htm,stm.htm,sym.htm,var.htm

    src HEAD ; Start of module interface.

    SRC STRUC          ; +00h.
    .FsSrcNamePtr  D D ; Pointer to a string with default (envelope) program name derived from source filename.
    .FsSrcNameSize D D ; Default program name size.
    .Errorlevel    D D ; Highest errorlevel reached in this source.
    .Inclusions    D D ; Number of succesfull INCLUDE* statements in this source.
                       ; +10h.
    .Pool          D D ; Src's memory pool.
    .CtxStack      D D ; ^STACK of context.
    .EaoptStack    D D ; ^STACK of EAOPT maintained with EUROASM PUSH/POP.
    .HeadStack     D D ; ^STACK of CHUNK_HEAD for nested HEAD/ENDHEAD blocks.
                       ; +20h.
    .VarList       D D ; ^LIST of %variables.
    .ChunkList     D D ; ^LIST of source chunks.
    .FileList      D D ; ^LIST of FILEs included in this source.
    .PfList        D D ; ^LIST of QWORDS (Ptr,Size) of output file names (used for detection of W3990).
                       ; +30h.
    .CurrentStm    D D ; ^STM currently executed. Used in MsgProc.
    .MemPeakPgm    D D ; Memory allocated by Pgm.Pool.
    .Lst           DS LST    ; Listing object.
    .Eaopt         DS EAOPT  ; Current €ASM options in charge.
    .Pgmopt        DS PGMOPT ; Current program options used as default.
    .IniFile       DS FILE   ; Local option file.
    .LstFile       DS FILE   ; Listing file.
    ENDSTRUC SRC

      ENDHEAD src ; End of module interface.

    [.bss]
    Src:: DS SRC ; The source object.
    [.text]

↑ SrcCreate
  This constructor initializes memory structures of the source.
  SrcCreate will also process local configuration file euroasm.ini, if found.
Input
  Initialized Ea object containing source file name and its suboperations.
Output
  CF=0, SRC object ready to assemble.
Error
  CF=1 Errors are reported with macro Msg.
Called from
  EaAssemble.
Invokes
  ChunkSuboperate EaBufferRelease EaBufferReserve EaFs2Id EaIniAssemble LstCreate
Invoked by
  EaAssemble

    SrcCreate:: Procedure
    Chunk     LocalVar Size=SIZE#CHUNK
    FileName$ LocalVar Size=MAX_PATH_SIZE  ; Room for local euroasm.ini filename.
         ; Initialize memory pool.
         Clear Src
         MOV EDI,%EaPoolSize
         PoolCreate Size=EDI, ErrorHandler=EaMallocError::
         JC .90:
         MOV [Src.Pool],EAX
         LEA EBX,[Ea.SrcFile::]
         LEA EDI,[EBX+FILE.Name]
         CMPW [EDI],'./'
         JNE .10:
         LEA ESI,[EDI+2]
         MOV ECX,SIZE#FILE.Name-2
         REP MOVSB
    .10: ; Derive default program name Src.FsSrcName from the source filename.
         LEA ESI,[EBX+FILE.Name]
         FileNameParse ESI
         MOV ESI,EAX
         SUB ECX,ESI
         ; ESI,ECX is now source file name without path and extension.
         Invoke EaBufferReserve::,SrcCreate
         Invoke EaFs2Id::,ESI,ECX,EAX      ; Create an identifier from file name.
         BufferRetrieve EAX
         Invoke EaBufferRelease::,EAX
         PoolStore [Src.Pool],ESI,ECX      ; Permanently store derived name ESI,ECX.
         MOV [Src.FsSrcNamePtr],EAX        ; Will be used as default (envelope) program name.
         MOV [Src.FsSrcNameSize],ECX
        ; Create memory structures of the source on source pool.
         MOV EDX,[Src.Pool]
         StackCreate EDX,SIZE#CTX,Depth=32
         MOV [Src.CtxStack],EAX
         StackCreate EDX,SIZE#EAOPT,Depth=4
         MOV [Src.EaoptStack],EAX
         StackCreate EDX,SIZE#CHUNK_HEAD,Depth=4
         MOV [Src.HeadStack],EAX
         ListCreate EDX,SIZE#VAR
         MOV [Src.VarList],EAX
         ListCreate EDX,8
         MOV [Src.PfList],EAX              ; Output file names are kept here.
         ListCreate EDX,SIZE#CHUNK
         MOV [Src.ChunkList],EAX
         ListCreate EDX,SIZE#FILE
         MOV [Src.FileList],EAX            ; Included files are kept in Src.FileList.
         Invoke LstCreate::                ; Initialize Src.Lst object.
    ; Three chunks will initialize assembly:
    ; 1. "FsSrcName: PROGRAM" stored on Src.Pool (chunkEnvelope).
    ; 2.  contents of base source file mapped in memory (chunkSource).
    ; 3. "ENDPROGRAM FsSrcName:" stored in Src.Pool (chunkEnvelope).
         ; First create chunk Nr.1:
         LEA EBX,[%Chunk]
         LEA EAX,[Ea.SrcFile::]
         MOV [EBX+CHUNK.FilePtr],EAX       ; The main source.
         MOV ECX,[Src.FsSrcNameSize]       ; Create chunk contents on Src.Pool.
         LEA EDX,[ECX+11]
         PoolNew [Src.Pool],EDX, Align=BYTE
         MOV EDI,EAX                       ; Room for envelope PROGRAM statement.
         MOV [EBX+CHUNK.Bottom],EAX
         MOV ESI,[Src.FsSrcNamePtr]
         REP MOVSB
         MOV AX,": "
         STOSW
         MOV ESI,[Dict_PseudoPROGRAM:: + DICT.Ptr] ; The text "PROGRAM".
         MOV ECX,[Dict_PseudoPROGRAM:: + DICT.Size]
         REP MOVSB
         MOV AX,0x0A0D ; CR+LF.
         STOSW
         MOV [EBX+CHUNK.Top],EDI
         MOVD [EBX+CHUNK.Status],chunkEnvelope
         ListStore [Src.ChunkList],EBX
         ; Create chunk Nr.2:
         LEA EBX,[%Chunk]
         MOV EAX,[EBX+CHUNK.FilePtr]
         LEA EDX,[EAX+FILE.Name]           ; "./source.asm"  instead of "source.asm" in Linux version.
         CMPW [EDX],'./'
         JNE .20:
         INC EDX,EDX
    .20: SysOpenFileMap EAX,EDX
    .30: Msg cc=C,'8030',EDX               ; Error reading source !1$.
         JC .90:
         ; The main source file contents is now mapped in memory ESI,EAX.
         MOV ECX,[ESI]
         AND ECX,0x00FFFFFF                ; Mask off the 4th byte.
         CMP ECX,0x00BFBBEF                ; Is it UTF-8 BOM?
         JNE .40:
         ADD ESI,3                         ; Ignore BOM.
         SUB EAX,3
         JBE .30:                          ; Source file is too short.
    .40: MOV [EBX+CHUNK.Bottom],ESI
         ADD EAX,ESI
         MOV [EBX+CHUNK.Top],EAX
         MOVD [EBX+CHUNK.Status],chunkSource
         ; Source file might have been specified with suboperation, e.g. "source.asm"{1..%&-80}[10..99]
         MOV ECX,[Ea.SubSize::]
         JECXZ .60:                        ; If no suboperation requested.
         MOV EAX,[Ea.SubPtr::]
         ADD ECX,EAX                       ; EAX..ECX now specifies the string, say {1..%&-80}[10..99]
    .50: Invoke ChunkSuboperate::,EBX,EAX,ECX,1
         JC .90:
         JZ .50:                           ; If chunk was suboperated, more chained suboperations may follow.
    .60: ListStore [Src.ChunkList],EBX
         MOV ECX,[Src.FsSrcNameSize]       ; Now create chunk Nr.3:
         LEA EDX,[ECX+13]
         PoolNew [Src.Pool],EDX, Align=BYTE
         MOV EDI,EAX
         MOV [EBX+CHUNK.Bottom],EAX
         MOV ESI,[Dict_PseudoENDPROGRAM:: + DICT.Ptr]       ; The text "ENDPROGRAM".
         MOV ECX,[Dict_PseudoENDPROGRAM:: + DICT.Size]      ; 9.
         REP MOVSB
         MOV AL,' '
         STOSB
         MOV ESI,[Src.FsSrcNamePtr]
         MOV ECX,[Src.FsSrcNameSize]
         REP MOVSB
         MOV AL,':'
         STOSB
         MOV AX,0x0A0D ; CR+LF.
         STOSW
         MOV [EBX+CHUNK.Top],EDI
         MOVD [EBX+CHUNK.Status],chunkEnvelope
         ListStore [Src.ChunkList],EBX
         ; Current options are inherited from the parent object (Ea).
         CopyTo Src.Eaopt,  Ea.EaoptIni::
         CopyTo Src.Pgmopt, Ea.Pgmopt::
         ; Then the options will be updated from local euroasm.ini file, if found.
         MOV ESI,Ea.SrcFile.Name::
         MOV ECX,[Ea.SrcFile.NameOffs::]
         LEA EDI,[%FileName$]
         PUSH EDI
          REP MOVSB                        ; Concantenate source path and "euroasm.ini".
          MOV ESI,=B"euroasm.ini"
          MOV ECX,12
          REP MOVSB
         POP EDI
         MOV EDX,Src.IniFile
         SysOpenFileMap EDX,EDI
         Msg cc=C,'0160',EDI               ; Local option file "!1$" was not found.
         JC .70:
         SetSt [EDX+FILE.Status],fileStFound
         Msg '0170',EDI                    ; Assembling local option file "!1S".
         ADD EAX,ESI
         Invoke EaIniAssemble:: ,ESI,EAX, Src.Eaopt, Src.Pgmopt
         SysCloseFile EDX
    .70: CopyTo Ea.Eaopt::,Src.Eaopt,Size=SIZE#EAOPT
         Msg '0180',Ea.SrcFile.Name, Ea.SubPtr:: ; Assembling source file "!1$"!2S.
    .90: EndProcedure SrcCreate

↑ SrcDestroy
  Assembly of a source file terminates.
Input
  -
Output
  EAX=amoun of memory allocated by the source.
Error
Called from
  EaAssemble.
Invokes
  LstGetFileName
Invoked by
  EaAssemble

    SrcDestroy Procedure
    LstSize    LocalVar
         Invoke LstGetFileName::
         LEA EBX,[Src.LstFile]
         SysAssignFile EBX,ESI,ECX
         FileMkDir EBX
         JNC .30:
    .LstError:
         LEA EAX,[EBX+FILE.Name]
         Msg '7982',EAX                    ; Error writing to listing file "!1$".
         JMP .40:
    SrcDestroy.WriteStreamBlock PROC       ; Callback from StreamDump macro.
             ADD [%LstSize],ECX
             FileWrite Src.LstFile,ESI,ECX
             RET
         ENDP SrcDestroy.WriteStreamBlock
    .30: FileCreate EBX                    ; Write listing to ListFile.
         JC .LstError:
         MOVD [%LstSize],0                 ; File size counter.
         LEA EDX,[Src.Lst]
         StreamDump [EDX+LST.Stream], .WriteStreamBlock
         JC .LstError:
         LEA EAX,[EBX+FILE.Name]
         Msg  '0860',EAX,[%LstSize]        ; Listing file "!1$" created, size=!2D.
    .40: FileClose EBX                     ; Close the listing file.
    .50: CopyTo Ea.Eaopt::, Src.Eaopt, Size=SIZE#EAOPT ; Ea.Eaopt is updated from Src.Eaopt.
         ListGetLast [Src.FileList]
         JZ .80:
    .70: SysCloseFile EAX                  ; Close all included files.
         ListGetPrev EAX
         JNZ .70:
         SysCloseFile Ea.SrcFile::         ; Close the base source file.
    .80: MOV EDX,[Src.Pool]
         MOV [Src.CtxStack],0
         PoolDestroy EDX
         Msg cc=C,'2575','Src',EDX         ; Deallocation of virtual memory !1C.Pool !2Hh failed.
         ADD EAX,[Src.MemPeakPgm]
         MOV [%ReturnEAX],EAX
    .90: Clear Src
        EndProcedure SrcDestroy

↑ SrcFetchLine LinePtr, ChunkPtr
  SrcFetchLine will find the memory-mapped physical line which starts at LinePtr and strip off its machine comment, if any. Searching for the chunk, where the required line belongs, starts with the specified chunk, or with the first chunk if ChunkPtr=NULL.
  If the chunk specified with LinePtr, ChunkPtr is `chunkBin` or `chunkError`, the returned data ESI..EAX is not source line but binary data or Msg parameter. The caller should test chunk status first.
Input
  LinePtr is pointer inside the text of source file mapped in memory, usually the start of a physical line.
  LinePtr may be 0, and the 1st physical line of the specified chunk is returned in this case.
  ChunkPtr is pointer to CHUNK where the search for line should start. ChunkPtr may be 0 which refers to the first chunk on Src.ChunkList.
Output on standard line
  CF=ZF=0
  ESI= Pointer to the beginning of line (if no machine comment), or behind the machine comment (behind `|` which terminates it).
  EAX= Pointer to the end of the returned physical line, usually right behind its LF.
  LF may be omitted when the last line in source file is not terminated with LineFeed.
  EDX= Pointer to the chunk the line lies in.
  Fetched line between ESI..EAX may be empty or white-space only.
Output on markup line
  CF=0, ZF=1 when the line is markup comment (starting with `<`) or when the whole line is machine comment (starting with `|`)
  The caller should invoke SrcFetchLine again with LinePtr=EAX, ChunkPtr=EDX.
  EAX=ESI= pointer to the end of the returned physical line, usually right behind LF.
  EDX= Pointer to the chunk where the line lies in.
Output on end of the last chunk
  CF=1, ZF=1 when there is no more source text.
  EAX, ESI, EDX unspecified.
Invoked by
  StmParse

    SrcFetchLine Procedure LinePtr, ChunkPtr
         MOV EAX,[%ChunkPtr]
         MOV EDI,[%LinePtr]
         TEST EAX
         JNZ .10:
         ListGetFirst [Src.ChunkList]
    .10: JZ .EndSource:                    ; No more chunks.
         TEST EDI
         JNZ .20:
         MOV EDI,[EAX+CHUNK.Bottom]
    .20: CMP EDI,[EAX+CHUNK.Bottom]
         JB .30:
         CMP EDI,[EAX+CHUNK.Top]
         JB .50:
         JE .40:
    .30: ListGetNext EAX
         JMP .10
    .40: ListGetNext EAX                   ; TxtPtr was at the top of current chunk, lets start at the bottom of the next one.
         JZ .EndSource:
         MOV EDI,[EAX+CHUNK.Bottom]
    .50: MOV [%ReturnEDX],EAX              ; EAX=actual chunk; EDI=start of physical line
         MOV ECX,[EAX+CHUNK.Top]
         MOV ESI,EDI
         SUB ECX,EDI
         JZ .40:
         JNSt [EAX+CHUNK.Status],chunkBin|chunkError,.60:
         MOV ECX,[EAX+CHUNK.Top]           ; Chunk EAX does not contain source lines but binary data or Msg parameter.
         MOV [%ReturnESI],EDI
         MOV [%ReturnEAX],ECX
         JMP .NormalOutput:
    .60: MOV AL,10
         REPNE SCASB                       ; Find the end of physical line.
         MOV [%ReturnESI],ESI
         MOV [%ReturnEAX],EDI
         MOV ECX,EDI                       ; Physical line ESI..EDI is specified. Now strip off comments.
         MOV EDI,ESI
         SUB ECX,ESI
    .70: LODSB                             ; Skip leading white spaces in the line of text.
         DEC ECX
         JZ .NormalOutput:
         ExpClassify AL
         TEST AH,expWhiteSpace
         JNZ .70:
         CMP AL,'<'                        ;>
         JE .CommentOnly:
         CMP AL,'|'
         JNE .NormalOutput:
         MOV EDI,ESI
         REPNE SCASB
         MOV [%ReturnESI],EDI
         JE .NormalOutput:
    .CommentOnly:
         MOV AH,flagZ                      ; ZF=1, CF=0
         JMPS .90:
    .EndSource:
         MOV AH,flagZ+flagC                ; ZF=CF=1
         JMPS .90:
    .NormalOutput:
         MOV AH,0                          ; ZF=CF=0
    .90: SAHF
        EndProcedure SrcFetchLine

↑ SrcAssemble
  SrcAssemble reads all lines from source and creates output program files. Assembly starts with the first line of first chunk on Src.ChunkList (which is the envelope PROGRAM statement).
  SrcAssemble stops when the last statement of the last chunk has been assembled (envelope ENDPROGRAM statement), or when source-fatal error occured (errorlevel >=8).
  Each statement from ordinary source chunk is parsed, executed, listed and flushed.
Source statement processing object model
  class="PRE">EaAssemble SrcCreate SrcAssemble StmCreate StmClean ┐ StmParse │ StmExecute │ repeated for each statement in the source. StmListing │ StmFlush ┘ StmDestroy SrcDestroy
See also
  Ea object model
Input
  Source must have been initialized with SrcCreate , context stack empty, chunk list loaded with three chunks: suboperated memory-mapped source file and both envelope chunks.
Output
  Source file is assembled.
Invokes
  CtxGetEndTypename CtxPeek StmClean StmCreate StmDestroy StmDisplayParsed StmExecute StmFlush StmListing StmParse
Invoked by
  EaAssemble

    SrcAssemble:: Procedure
    Stm  LocalVar Size=SIZE#STM                      ; Stm object used for statement parsing.
         LEA EBX,[%Stm]
         Invoke StmCreate::,EBX
         SUB EDX,EDX
         SUB EAX,EAX                                 ; LinePtr=0, start with the 1st line of source, which is envelope "file: PROGRAM".
    .10: ; Here is the main loop which will read and execute all statements in the source.
         Invoke StmClean::,EBX                       ; Empty statement buffers and erase remains of previous statement.
         MOV [Src.CurrentStm],EBX
         Invoke StmParse::,EBX,EAX,EDX               ; Fetch and parse statement adressed with EAX.
         JC .60:                                     ; If no more source lines.
         INCD [Ea.StmCount::]                        ; Counter of total assembled statements.
         JNSt [Ea.Eaopt.Status::],eaoptDISPLAYSTM,.18:
         Invoke StmDisplayParsed::,EBX               ; Diagnostic info how was the statement parsed.
     .18:Invoke StmExecute::,EBX                     ; Execute the statement: fill statement buffers, modify context stack, create symbols etc.
         XOR ECX,ECX
         XOR EAX,EAX
     .20:Invoke CtxPeek::,ctxPROGRAM,EAX             ; Find emitting program context.
         JC .30:                                     ; Skip when envelope ENDPROGRAM was just executed. No more programs on stack.
         MOV ECX,[EAX+CTX.ObjPtr]                    ; ECX=0 when PROGRAM statement was in NoEmit state.
         JECXZ .20:                                  ; If NoEmit, search the stack deeper.
         JNSt [ECX+PGM.Status],pgmLastPass,.50:      ; Skip the listing in nonlast passes.
         JSt [ECX+PGM.Status],pgmLastJustSet,.40:
    .30: Invoke StmListing::,EBX                     ; Create a listing line(s) and write to Lst.Stream.
    .40: JECXZ .50:
         RstSt [ECX+PGM.Status],pgmLastJustSet
    .50: RstSt [Src.Lst.Status],lstVolMask           ; Clear used volatile flags of listing.
         Invoke StmFlush::,EBX                       ; Flush STM.EmitBuffer to the current section's SSS.EmitBuffer.
         CMP [Src.Errorlevel],8
         JAE .80:                                    ; Abort if source-fatal error. The source file will be prematurely abandoned.
         MOV EAX,[EBX+STM.LineNext]                  ; The next statement where to continue.
         MOV EDX,[EBX+STM.ChunkNext]
         TEST EAX
         JNZ .10:                                    ; If STM.LineNext is specified, go to assemble this new statement (macro or repeat block).
         ; Otherwise continue with the next statement in the normal source flow.
         MOV EAX,[EBX+STM.LineEnd]                   ; End of just executed statement is the beginning of the next one.
         MOV EDX,[EBX+STM.ChunkPtr]
         JMP .10:
    .60: ; End of source text.
         StackPop [Src.CtxStack]                     ; Check if no context was left on stack.
         JC .80:                                     ; It should be empty.
         MOV EDI,EAX                                 ; Some block in source was left open, this is an error.
         Invoke CtxGetEndTypename::,[EDI+CTX.Status] ; EAX=^DQ Ptr,Size
         JNSt [EDI+CTX.Status],ctxREPEAT,.70:
         JSt [EDI+CTX.Status],ctxExited,.70:
         XCHG EAX,EDI                                ; Operation %REPEAT has block identifier in %1 instead of label field.
    .70: Msg '7110',EAX,EDI                          ;  Wrong nesting, expected !1S !2S.  ENDblock id
         JMP .60:
    .80: Invoke StmDestroy::,EBX
         MOVD [Src.CurrentStm],0
    .90: EndProcedure SrcAssemble::

↑ SrcPosition LinePtr
  SrcPosition returns pointer to the source FILE (main source or included file or configuration file) and the physical line number to which LinePtr is currently pointing in memory-mapped file contents.
Input
  LinePtr Pointer to source line in memory-mapped file.
Output
  CF=0
  EAX= physical line number (1 or greater).
  EDX= pointer to FILE.
  EDI= pointer to ASCIIZ file name without path.
Error
  CF=1 if file not found or if it is an envelope pseudosource.
  EAX= 0.
  EDX= 0.
  EDI= pointer to NULL byte.
Invoked by
  MsgProc VarExpand

    SrcPosition Procedure LinePtr
         MOV EBX,[%LinePtr]
         MOV EAX,Ea.SrcFile::
         CALL .TryFileEAX:
         JNC .Found:
         ListGetFirst [Src.FileList]
         JZ .20:
     .10:CALL .TryFileEAX:
         JNC .Found:
         ListGetNext EAX
         JNZ .10:
     .20:MOV EAX,Ea.IniFile::              ; Try global "euroasm.ini".
         CALL .TryFileEAX:
         JNC .Found:
         MOV EAX,Src.IniFile               ; Try local "euroasm.ini".
         CALL .TryFileEAX:
         MOV EDI,=B(0)                     ; No source file found.
         SUB ESI,ESI
         MOV [%ReturnEDX],ESI
         STC
         JMP .90:
    SrcPosition.TryFileEAX: PROC           ; Check if LinePtr=EBX is in the mapped range of file EAX.
          ; Output: CF=1 if not in range. ECX=?
          MOV ECX,[EAX+FILE.BufPtr]        ; Bottom of mapped source.
          CMP EBX,ECX
          JB .T9:
          ADD ECX,[EAX+FILE.BufSize] ; Top of mapped source.
          CMP EBX,ECX                      ; Compare EBX=LinePtr with the top of source.
          CMC
      .T9:RET
        ENDP SrcPosition.TryFileEAX:
    .Found:
         MOV EDX,EAX                       ; ^FILE.
         MOV [%ReturnEDX],EAX              ; ^FILE.
         MOV EDI,[EDX+FILE.BufPtr]
         MOV ECX,[EDX+FILE.BufSize]
         SUB ESI,ESI                       ; Line counter.
         MOV AL,10                         ; End of physical line.
     .60:INC ESI
         JECXZ .80:
         REPNE SCASB                       ; Find EOL.
         CMP EBX,EDI                       ; Match with LinePtr?
         JAE .60:
     .80:LEA EDI,[EDX+FILE.Name]
         ADD EDI,[EDX+FILE.NameOffs]       ; Skip file path, if any. CF=0.
     .90:MOV [%ReturnEDI],EDI
         MOV [%ReturnEAX],ESI
        EndProcedure SrcPosition

       ENDPROGRAM src

▲Back to the top▲