Subversion Repositories Kolibri OS

Rev

Rev 2816 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. ; This program parses .fas file generated by fasm
  2. ; and prints the list of dependencies for make.
  3. ; Usage: fasmdep [-e] [<input file> [<output file>]].
  4. ; If input file is not given, the program reads from stdin.
  5. ; If output file is not given, the program writes to stdout.
  6. ; If the option -e is given, the program also creates an empty
  7. ; goal for every dependency, this prevents make errors when
  8. ; files are renamed.
  9.  
  10. ; This definition controls the choice of platform-specific code.
  11. ;define OS WINDOWS
  12. define OS LINUX
  13.  
  14. ; Program header.
  15. match =WINDOWS,OS { include 'windows_header.inc' }
  16. match =LINUX,OS { include 'linux_header.inc' }
  17.  
  18. include 'fas.inc'
  19.  
  20. ; Main code
  21. start:
  22. ; 1. Setup stack frame, zero-initialize all local variables.
  23. virtual at ebp - .localsize
  24. .begin:
  25. .flags          dd      ?       ; 1 if '-e' is given
  26. .in             dd      ?       ; handle of input file
  27. .out            dd      ?       ; handle of output file
  28. .buf            dd      ?       ; pointer to data of .fas file
  29. .allocated      dd      ?       ; number of bytes allocated for .buf
  30. .free           dd      ?       ; number of bytes free in .buf
  31. .outstart       dd      ?       ; offset in .buf for start of data to output
  32. .names          dd      ?       ; offset in .buf for start of output names
  33. .include        dd      ?       ; offset in .buf to value of %INCLUDE%
  34. .testname       dd      ?       ; offset in .buf for start of current file name
  35. .prevfile       dd      ?       ; offset in .buf for previous included file name
  36. .prevfilefrom   dd      ?       ; offset in .buf for .asm/.inc for .prevfile
  37. .localsize = $ - .begin
  38. match =LINUX,OS {
  39. .argc           dd      ?
  40. .argv:
  41. }
  42. end virtual
  43.  
  44.         mov     ebp, esp
  45.         xor     eax, eax
  46. repeat .localsize / 4
  47.         push    eax
  48. end repeat
  49. ; 2. Call the parser of the command line.
  50.         call    get_params
  51. ; 3. Load the input file.
  52. ; Note that stdin can be a pipe,
  53. ; useful in bash-construction "fasm input.asm -s >(fasmdep > input.Po)",
  54. ; so its size is not always known in the beginning.
  55. ; So, we must read input file in portions, reallocating memory if needed.
  56. .readloop:
  57. ; 3a. If the size is less than 32768 bytes, reallocate buffer.
  58. ; Otherwise, goto 3c.
  59.         cmp     [.free], 32768
  60.         jae     .norealloc
  61. ; 3b. Reallocate buffer.
  62. ; Start with 65536 bytes and then double the size every time.
  63.         mov     eax, 65536
  64.         mov     ecx, [.allocated]
  65.         add     ecx, ecx
  66.         jz      @f
  67.         mov     eax, ecx
  68. @@:
  69.         call    realloc
  70. .norealloc:
  71. ; 3c. Read the next portion.
  72.         call    read
  73.         sub     [.free], eax
  74.         test    eax, eax
  75.         jnz     .readloop
  76. ; 4. Sanity checks.
  77. ; We don't use .section_* and .symref_*, so allow them to be absent.
  78.         mov     edi, [.allocated]
  79.         sub     edi, [.free]
  80. ; Note that edi = number of bytes which were read
  81. ; and simultaneously pointer to free space in the buffer relative to [.buf].
  82.         cmp     edi, fas_header.section_offs
  83.         jb      badfile
  84.         mov     ebx, [.buf]
  85.         cmp     [ebx+fas_header.signature], FAS_SIGNATURE
  86.         jnz     badfile
  87.         cmp     [ebx+fas_header.headersize], fas_header.section_offs
  88.         jb      badfile
  89. ; 5. Get %INCLUDE% environment variable, it will be useful.
  90.         mov     [.include], edi
  91.         mov     esi, include_variable
  92.         sub     esi, ebx
  93.         call    get_environment_variable
  94. ; 6. Start writing dependencies: copy output and input files.
  95.         mov     [.outstart], edi
  96. ; 6a. Copy output name.
  97.         mov     esi, [ebx+fas_header.output]
  98.         call    copy_asciiz_escaped
  99. ; 6b. Write ": ".
  100.         stdcall alloc_in_buf, 2
  101.         mov     word [edi+ebx], ': '
  102.         inc     edi
  103.         inc     edi
  104. ; 6c. Copy input name.
  105.         mov     [.names], edi
  106.         mov     esi, [ebx+fas_header.input]
  107.         call    copy_asciiz_escaped
  108. ; 7. Scan for 'include' dependencies.
  109. ; 7a. Get range for scanning.
  110.         mov     edx, [ebx+fas_header.preproc_size]
  111.         mov     esi, [ebx+fas_header.preproc_offs]
  112.         add     edx, esi
  113. .include_loop:
  114. ; 7b. Loop over preprocessed lines in the range.
  115.         cmp     esi, edx
  116.         jae     .include_done
  117. ; 7c. For every line, ignore header and do internal loop over tokens.
  118.         add     esi, preproc_line_header.contents
  119. .include_loop_int:
  120. ; There are five types of tokens:
  121. ; 1) "start preprocessor data" with code ';' <byte length> <length*byte token>
  122. ; 2) "quoted string" with code '"' <dword length> <length*byte string>
  123. ; 3) "word" with code 1Ah <byte length> <length*byte word>
  124. ; 4) one-byte tokens like "+", "(" and so on
  125. ; 5) "end-of-line" token with code 0.
  126.         mov     al, [esi+ebx]
  127.         inc     esi
  128. ; 7d. First, check token type parsing the first byte.
  129. ; For preprocessor tokens, continue to 7e.
  130. ; For quoted strings, go to 7g.
  131. ; For words, go to 7h.
  132. ; For end-of-line, exit the internal loop, continue the external loop.
  133. ; Otherwise, just continue the internal loop.
  134.         cmp     al, ';'
  135.         jnz     .notprep
  136. ; 7e. For "include" tokens length=7, token is "include", the next token is
  137. ; quoted string.
  138. ; These tokens are handled in 7f, other preprocessor tokens in 7g.
  139.         cmp     byte [esi+ebx], 7
  140.         jnz     .notinclude
  141.         cmp     dword [esi+ebx+1], 'incl'
  142.         jnz     .notinclude
  143.         cmp     dword [esi+ebx+5], 'ude"'
  144.         jnz     .notinclude
  145. ; 7f. Skip over end of this token and the type byte of the next token;
  146. ; this gives the pointer to length-prefixed string, which should be added
  147. ; to dependencies. Note that doing that skips that string,
  148. ; so after copying just continue the internal loop.
  149.         add     esi, 9
  150.         call    add_separator
  151.         call    copy_name_escaped
  152.         jmp     .include_loop_int
  153. .quoted:
  154. ; 7g. To skip a word, load the dword length and add it to pointer.
  155.         add     esi, [esi+ebx]
  156.         add     esi, 4
  157.         jmp     .include_loop_int
  158. .notinclude:
  159. ; 7h. To skip this token, load the byte length and add it to pointer.
  160. ; Note that word tokens and preprocessor tokens have a similar structure,
  161. ; so both are handled here.
  162.         movzx   eax, byte [esi+ebx]
  163.         lea     esi, [esi+eax+1]
  164.         jmp     .include_loop_int
  165. .notprep:
  166.         cmp     al, 1Ah
  167.         jz      .notinclude
  168.         cmp     al, '"'
  169.         jz      .quoted
  170.         test    al, al
  171.         jnz     .include_loop_int
  172.         jmp     .include_loop
  173. .include_done:
  174. ; 8. Scan for 'file' dependencies.
  175. ; 8a. Get range for scanning.
  176. ; Note that fas_header.asm_size can be slighly greater than the
  177. ; real size, so we break from the loop when there is no space left
  178. ; for the entire asm_row structure, not when .asm_size is exactly reached.
  179.         mov     esi, [ebx+fas_header.asm_offs]
  180.         mov     edx, [ebx+fas_header.asm_size]
  181.         add     edx, esi
  182.         sub     edx, asm_row.sizeof
  183.         push    edx
  184.         jc      .file_done
  185. .file_loop:
  186. ; 8b. Loop over assembled lines in the range.
  187.         cmp     esi, [esp]
  188.         ja      .file_done
  189. ; 8c. For every assembled line, skip "word" and one-byte tokens
  190. ; scanning for three predefined word tokens.
  191. ; Note that lines like "a:b:c:d file 'something'" are possible and valid.
  192. ; go to 8d for token 'file',
  193. ; go to 8e for token 'format',
  194. ; go to 8f for token 'section',
  195. ; continue the loop otherwise.
  196.         mov     eax, [esi+ebx+asm_row.preproc_offs]
  197.         add     eax, [ebx+fas_header.preproc_offs]
  198. ; We save/restore esi since it is used for another thing in the internal loop;
  199. ; we push eax, which currently contains ebx-relative pointer to
  200. ; preproc_line_header, to be able to access it from resolve_name.
  201.         push    esi eax
  202. .file_tokens_loop:
  203.         mov     cl, byte [eax+ebx+preproc_line_header.contents]
  204.         cmp     cl, 1Ah
  205.         jnz     .file_noword_token
  206.         movzx   ecx, byte [eax+ebx+preproc_line_header.contents+1]
  207.         cmp     cl, 4
  208.         jnz     .file_no_file
  209.         cmp     dword [eax+ebx+preproc_line_header.contents+2], 'file'
  210.         jnz     .file_no_file4
  211. ; 8d. For lines starting with 'file' token, loop over tokens and get names.
  212. ; Note that there can be several names in one line.
  213. ; Parsing of tokens is similar to step 5 with the difference that
  214. ; preprocessor token stops processing: 'file' directives are processed
  215. ; in assembler stage.
  216.         lea     esi, [eax+preproc_line_header.contents+6]
  217. .file_loop_int:
  218.         mov     al, [esi+ebx]
  219.         inc     esi
  220.         test    al, al
  221.         jz      .file_next
  222.         cmp     al, ';'
  223.         jz      .file_next
  224.         cmp     al, 1Ah
  225.         jz      .fileword
  226.         cmp     al, '"'
  227.         jnz     .file_loop_int
  228.         call    resolve_name
  229.         add     esi, [esi+ebx-4]
  230.         jmp     .file_loop_int
  231. .fileword:
  232.         movzx   eax, byte [esi+ebx]
  233.         lea     esi, [esi+eax+1]
  234.         jmp     .file_loop_int
  235. .file_no_file4:
  236.         cmp     dword [eax+ebx+preproc_line_header.contents+2], 'data'
  237.         jnz     .file_word_token
  238.         jmp     .file_scan_from
  239. .file_no_file:
  240.         cmp     cl, 6
  241.         jnz     .file_no_format
  242.         cmp     dword [eax+ebx+preproc_line_header.contents+2], 'form'
  243.         jnz     .file_no_format
  244.         cmp     word [eax+ebx+preproc_line_header.contents+6], 'at'
  245.         jnz     .file_no_format
  246. ; 8e. For lines starting with 'format' token, loop over tokens and look for stub name.
  247. ; Note that there can be another quoted string, namely file extension;
  248. ; stub name always follows the token 'on'.
  249.         mov     edx, TokenOn
  250.         call    scan_after_word
  251.         jmp     .file_next
  252. .file_no_format:
  253.         cmp     cl, 7
  254.         jnz     .file_no_section
  255.         cmp     dword [eax+ebx+preproc_line_header.contents+2], 'sect'
  256.         jnz     .file_no_section
  257.         mov     edx, dword [eax+ebx+preproc_line_header.contents+6]
  258.         and     edx, 0FFFFFFh
  259.         cmp     edx, 'ion'
  260.         jnz     .file_no_section
  261. .file_scan_from:
  262.         mov     edx, TokenFrom
  263.         call    scan_after_word
  264.         jmp     .file_next
  265. .file_no_section:
  266. .file_word_token:
  267.         lea     eax, [eax+ecx+2]
  268.         jmp     .file_tokens_loop
  269. .file_noword_token:
  270.         inc     eax
  271.         test    cl, cl
  272.         jz      .file_next
  273.         cmp     cl, 3Bh
  274.         jz      .file_next
  275.         cmp     cl, 22h
  276.         jnz     .file_tokens_loop
  277. .file_next:
  278.         pop     eax esi
  279.         add     esi, asm_row.sizeof
  280.         jmp     .file_loop
  281. .file_done:
  282.         pop     edx
  283. ; 9. Write result.
  284. ; 9a. Append two newlines to the end of buffer.
  285.         stdcall alloc_in_buf, 2
  286.         mov     word [edi+ebx], 10 * 101h
  287.         inc     edi
  288.         inc     edi
  289. ; 9b. If '-e' option was given, duplicate dependencies list as fake goals
  290. ; = copy all of them and append ":\n\n".
  291.         cmp     [.flags], 0
  292.         jz      .nodup
  293.         lea     ecx, [edi+1]
  294.         mov     esi, [.names]
  295.         sub     ecx, esi
  296.         stdcall alloc_in_buf, ecx
  297.         add     esi, ebx
  298.         add     edi, ebx
  299.         rep     movsb
  300.         mov     byte [edi-3], ':'
  301.         mov     word [edi-2], 10 * 101h
  302.         sub     edi, ebx
  303. .nodup:
  304. ; 9c. Write to output file.
  305.         mov     eax, [.outstart]
  306.         sub     edi, eax
  307.         add     eax, ebx
  308.         call    write
  309. ; 10. Exit.
  310.         xor     eax, eax
  311.         call    exit
  312.  
  313. ; Helper procedure for steps 8e and 8f of main algorithm.
  314. ; Looks for quoted strings after given word in edx.
  315. scan_after_word:
  316.         push    esi eax
  317.         lea     esi, [eax+preproc_line_header.contents+2+ecx]
  318. .loop:
  319.         xor     ecx, ecx
  320. .loop_afterword:
  321.         mov     al, [esi+ebx]
  322.         inc     esi
  323.         test    al, al
  324.         jz      .loop_done
  325.         cmp     al, ';'
  326.         jz      .loop_done
  327.         cmp     al, 1Ah
  328.         jz      .word
  329.         cmp     al, '"'
  330.         jnz     .loop
  331.         test    ecx, ecx
  332.         jz      .skip_quoted
  333.         call    resolve_name
  334. .loop_done:
  335.         pop     eax esi
  336.         ret
  337. .skip_quoted:
  338.         add     esi, [esi+ebx]
  339.         add     esi, 4
  340.         jmp     .loop
  341. .word:
  342.         movzx   ecx, byte [esi+ebx]
  343.         lea     esi, [esi+ecx+1]
  344.         cmp     cl, byte [edx]
  345.         jnz     .loop
  346.         push    esi edi
  347.         add     esi, ebx
  348.         sub     esi, ecx
  349.         lea     edi, [edx+1]
  350.         repz    cmpsb
  351.         pop     edi esi
  352.         jnz     .loop
  353.         dec     ecx
  354.         jmp     .loop_afterword
  355.  
  356. ; Helper procedure for step 6 of the main procedure.
  357. ; Copies the ASCIIZ name from strings section to the buffer.
  358. copy_asciiz_escaped:
  359.         add     esi, [ebx+fas_header.strings_offs]
  360. .loop:
  361.         mov     al, [esi+ebx]
  362.         test    al, al
  363.         jz      .nothing
  364.         call    copy_char_escaped
  365.         jmp     .loop
  366. .nothing:
  367.         ret
  368.  
  369. ; Helper procedure for step 7 of the main procedure.
  370. ; Copies the name with known length to the buffer.
  371. copy_name_escaped:
  372.         mov     ecx, [esi+ebx]
  373.         add     esi, 4
  374.         test    ecx, ecx
  375.         jz      .nothing
  376.         push    ecx
  377. .loop:
  378.         mov     al, [esi+ebx]
  379.         call    copy_char_escaped
  380.         dec     dword [esp]
  381.         jnz     .loop
  382.         pop     ecx
  383. .nothing:
  384.         ret
  385.  
  386. ; Helper procedure for steps 7 and 8 of the main procedure.
  387. ; Writes separator of file names in output = " \\\n".
  388. add_separator:
  389.         stdcall alloc_in_buf, 3
  390.         mov     word [edi+ebx], ' \'
  391.         mov     byte [edi+ebx+2], 10
  392.         add     edi, 3
  393.         ret
  394.  
  395. ; Helper procedure for step 7 of the main procedure.
  396. ; Resolves the path to 'file' dependency and copies
  397. ; the full name to the buffer.
  398. resolve_name:
  399. ; FASM uses the following order to search for referenced files:
  400. ; * path of currently assembling file, which may be .asm or .inc
  401. ; * paths from %INCLUDE% for versions >= 1.70
  402. ; * current directory = file name is taken as is, without prepending dir name
  403. ; We mirror this behaviour, trying to find an existing file somewhere.
  404. ; There can be following reasons for the file can not be found anywhere:
  405. ; * it has been deleted between compilation and our actions
  406. ; * it didn't exist at all, compilation has failed
  407. ; * we are running in environment different from fasm environment.
  408. ; Assume that the last reason is most probable and that invalid dependency
  409. ; is better than absent dependency (it is easier to fix an explicit error
  410. ; than a silent one) and output file name without prepending dir name,
  411. ; as in the last case. Actually, we even don't need to test existence
  412. ; of the file in the current directory.
  413.         add     esi, 4  ; skip string length
  414. ; 1. Get ebx-relative pointer to preproc_line_header, see the comment in start.7d
  415.         mov     eax, [esp+4]
  416. ; 2. Get the path to currently processing file.
  417.         push    esi
  418. .getpath:
  419.         test    byte [eax+ebx+preproc_line_header.line_number+3], 80h
  420.         jz      @f
  421.         mov     eax, [eax+ebx+preproc_line_header.line_offset]
  422.         add     eax, [ebx+fas_header.preproc_offs]
  423.         jmp     .getpath
  424. @@:
  425.         mov     edx, [eax+ebx+preproc_line_header.source_name]
  426.         test    edx, edx
  427.         jz      .frommain
  428.         add     edx, [ebx+fas_header.preproc_offs]
  429.         jmp     @f
  430. .frommain:
  431.         mov     edx, [ebx+fas_header.input]
  432.         add     edx, [ebx+fas_header.strings_offs]
  433. @@:
  434. ; 3. Check that it is not a duplicate of the previous dependency.
  435. ; 3a. Compare preprocessor units.
  436.         cmp     edx, [start.prevfilefrom]
  437.         jnz     .nodup
  438. ; 3b. Compare string lengths.
  439.         mov     eax, [start.prevfile]
  440.         mov     ecx, [eax+ebx-4]
  441.         cmp     ecx, [esi+ebx-4]
  442.         jnz     .nodup
  443. ; 3c. Compare string contents.
  444.         push    esi edi
  445.         lea     edi, [eax+ebx]
  446.         add     esi, ebx
  447.         rep     cmpsb
  448.         pop     edi esi
  449.         jnz     .nodup
  450. ; 3d. It is duplicate, just return.
  451.         pop     esi
  452.         ret
  453. .nodup:
  454. ; 3e. It is not duplicate. Output separator.
  455.         mov     [start.prevfilefrom], edx
  456.         mov     [start.prevfile], esi
  457.         call    add_separator
  458. ; 4. Cut the last component of the path found in step 2.
  459.         mov     ecx, edx
  460.         mov     esi, edx
  461. .scanpath:
  462.         mov     al, [edx+ebx]
  463.         test    al, al
  464.         jz      .scandone
  465.         cmp     al, '/'
  466.         jz      .slash
  467.         cmp     al, '\'
  468.         jnz     .scannext
  469. .slash:
  470.         lea     ecx, [edx+1]
  471. .scannext:
  472.         inc     edx
  473.         jmp     .scanpath
  474. .scandone:
  475.         sub     ecx, esi
  476. ; 5. Try path found in step 4. If found, go to step 8.
  477.         mov     [start.testname], edi
  478.         stdcall copy_string, esi, ecx
  479.         pop     esi
  480.         call    expand_environment
  481.         call    test_file_exists
  482.         test    eax, eax
  483.         jns     .found
  484.         call    revert_testname
  485. ; 6. Try each of include paths. For every path, if file is found, go to step 8.
  486. ; Otherwise, continue loop over include path.
  487. ; Skip this step before 1.70.
  488.         cmp     [ebx+fas_header.major], 1
  489.         ja      .checkenv
  490.         jb      .nocheckenv
  491.         cmp     [ebx+fas_header.minor], 70
  492.         jb      .nocheckenv
  493. .checkenv:
  494.         mov     ecx, [start.include]
  495. .includeloop_ext:
  496.         mov     eax, ecx
  497. .includeloop_int:
  498.         cmp     byte [eax+ebx], 0
  499.         jz      @f
  500.         cmp     byte [eax+ebx], ';'
  501.         jz      @f
  502.         inc     eax
  503.         jmp     .includeloop_int
  504. @@:
  505.         push    eax
  506.         sub     eax, ecx
  507.         jz      @f
  508.         stdcall copy_string, ecx, eax
  509.         cmp     byte [edi+ebx-1], '/'
  510.         jz      .hasslash
  511. @@:
  512.         stdcall alloc_in_buf, 1
  513.         mov     byte [edi+ebx], '/'
  514.         inc     edi
  515. .hasslash:
  516.         call    expand_environment
  517.         call    test_file_exists
  518.         pop     ecx
  519.         test    eax, eax
  520.         jns     .found
  521.         call    revert_testname
  522.         cmp     byte [ecx+ebx], 0
  523.         jz      .notfound
  524.         inc     ecx
  525.         cmp     byte [ecx+ebx], 0
  526.         jnz     .includeloop_ext
  527. .nocheckenv:
  528. .notfound:
  529. ; 7. File not found neither near the current preprocessor unit nor in %INCLUDE%.
  530. ; Assume that it is in the current directory.
  531.         call    expand_environment
  532. .found:
  533. ; 8. Currently we have file name from [start.testname] to edi;
  534. ; it is zero-terminated and not space-escaped. Fix both issues.
  535.         dec     edi
  536.         inc     [start.free]
  537.         push    esi
  538.         mov     edx, [start.testname]
  539. .escapeloop:
  540.         cmp     edx, edi
  541.         jae     .escapedone
  542.         cmp     byte [edx+ebx], ' '
  543.         jnz     .noescape
  544.         stdcall alloc_in_buf, 1
  545.         mov     ecx, edi
  546.         sub     ecx, edx
  547.         push    edi
  548.         add     edi, ebx
  549.         lea     esi, [edi-1]
  550.         std
  551.         rep     movsb
  552.         cld
  553.         pop     edi
  554.         inc     edi
  555.         mov     byte [edx+ebx], '\'
  556.         inc     edx
  557. .noescape:
  558.         inc     edx
  559.         jmp     .escapeloop
  560. .escapedone:
  561.         pop     esi
  562.         ret
  563.  
  564. ; Helper procedure for resolve_name.
  565. ; Allocates space in the buffer and appends the given string to the buffer.
  566. copy_string:
  567.         mov     eax, [esp+8]
  568.         test    eax, eax
  569.         jz      .nothing
  570.         stdcall alloc_in_buf, eax
  571.         mov     ecx, [esp+4]
  572. .copy:
  573.         mov     al, [ecx+ebx]
  574.         inc     ecx
  575.         cmp     al, '\'
  576.         jnz     @f
  577.         mov     al, '/'
  578. @@:
  579.         mov     [edi+ebx], al
  580.         inc     edi
  581.         dec     dword [esp+8]
  582.         jnz     .copy
  583. .nothing:
  584.         ret     8
  585.  
  586. ; Helper procedure for resolve_name. Undoes appending of last file name.
  587. revert_testname:
  588.         add     [start.free], edi
  589.         mov     edi, [start.testname]
  590.         sub     [start.free], edi
  591.         ret
  592.  
  593. ; Helper procedure for resolve_name. Copies string from esi to edi,
  594. ; expanding environment variables.
  595. expand_environment:
  596. ; 1. Save esi to restore it in the end of function.
  597.         push    esi
  598. ; 2. Push string length to the stack to be used as a variable.
  599.         pushd   [esi+ebx-4]
  600. ; 3. Scan loop.
  601. .scan:
  602. ; 3a. Scan for '%' sign.
  603.         call    find_percent
  604. .justcopy:
  605. ; 3b. Copy the part from the beginning of current portion to '%' sign,
  606. ; advance pointer to '%' sign, or end-of-string if no '%' found.
  607.         push    eax
  608.         sub     eax, esi
  609.         stdcall copy_string, esi, eax
  610.         pop     esi
  611. ; 3c. If string has ended, break from the loop.
  612.         cmp     dword [esp], 0
  613.         jz      .scandone
  614. ; 3d. Advance over '%' sign.
  615.         inc     esi
  616.         dec     dword [esp]
  617. ; 3e. Find paired '%'.
  618.         call    find_percent
  619. ; 3f. If there is no paired '%', just return to 3b and copy remaining data,
  620. ; including skipped '%'; after that, 3c would break from the loop.
  621.         dec     esi
  622.         cmp     dword [esp], 0
  623.         jz      .justcopy
  624. ; 3g. Otherwise, get the value of environment variable.
  625. ; Since get_environment_variable requires zero-terminated string
  626. ; and returns zero-terminated string, temporarily overwrite trailing '%'
  627. ; and ignore last byte in returned string.
  628. ; Also convert any backslashes to forward slashes.
  629.         inc     esi
  630.         mov     byte [eax+ebx], 0
  631.         push    eax
  632.         push    edi
  633.         call    get_environment_variable
  634.         dec     edi
  635.         inc     [start.free]
  636.         pop     eax
  637. .replaceslash:
  638.         cmp     eax, edi
  639.         jz      .replaceslash_done
  640.         cmp     byte [eax+ebx], '\'
  641.         jnz     @f
  642.         mov     byte [eax+ebx], '/'
  643. @@:
  644.         inc     eax
  645.         jmp     .replaceslash
  646. .replaceslash_done:
  647.         pop     esi
  648.         mov     byte [esi+ebx], '%'
  649. ; 3h. Advance over trailing '%'.
  650.         inc     esi
  651.         dec     dword [esp]
  652. ; 3i. Continue the loop.
  653.         jmp     .scan
  654. .scandone:
  655. ; 4. Zero-terminate resulting string.
  656.         stdcall alloc_in_buf, 1
  657.         mov     byte [edi+ebx], 0
  658.         inc     edi
  659. ; 5. Pop stack variable initialized in step 2.
  660.         pop     eax
  661. ; 6. Restore esi saved in step 1 and return.
  662.         pop     esi
  663.         ret
  664.  
  665. ; Helper procedure for expand_environment.
  666. ; Scans the string in esi with length [esp+4]
  667. ; until '%' is found or line ended.
  668. find_percent:
  669.         mov     eax, esi
  670.         cmp     dword [esp+4], 0
  671.         jz      .nothing
  672. .scan:
  673.         cmp     byte [eax+ebx], '%'
  674.         jz      .nothing
  675.         inc     eax
  676.         dec     dword [esp+4]
  677.         jnz     .scan
  678. .nothing:
  679.         ret
  680.  
  681. ; Helper procedure for copy_{name,asciiz}_escaped.
  682. ; Allocates space and writes one character, possibly escaped.
  683. copy_char_escaped:
  684.         cmp     al, ' '
  685.         jnz     .noescape
  686.         stdcall alloc_in_buf, 1
  687.         mov     byte [edi+ebx], '\'
  688.         inc     edi
  689. .noescape:
  690.         stdcall alloc_in_buf, 1
  691.         mov     al, [esi+ebx]
  692.         inc     esi
  693.         cmp     al, '\'
  694.         jnz     @f
  695.         mov     al, '/'
  696. @@:
  697.         mov     [edi+ebx], al
  698.         inc     edi
  699.         ret
  700.  
  701. ; Helper procedure for ensuring that there is at least [esp+4]
  702. ; free bytes in the buffer.
  703. alloc_in_buf:
  704.         mov     eax, [esp+4]
  705.         sub     [start.free], eax
  706.         jb      .need_realloc
  707.         ret     4
  708. .need_realloc:
  709.         mov     eax, [start.allocated]
  710.         add     eax, eax
  711.         push    ecx edx
  712.         call    realloc
  713.         pop     edx ecx
  714.         cmp     [start.free], 0
  715.         jl      .need_realloc
  716.         mov     ebx, [start.buf]
  717.         ret     4
  718.  
  719. badfile:
  720.         mov     esi, badfile_string
  721.         call    sayerr
  722.         mov     al, 1
  723.         call    exit
  724.  
  725. information:
  726.         mov     esi, information_string
  727.         call    sayerr
  728.         mov     al, 2
  729.         call    exit
  730.  
  731. nomemory:
  732.         mov     esi, nomemory_string
  733.         call    sayerr
  734.         mov     al, 3
  735.         call    exit
  736.  
  737. in_openerr:
  738.         mov     esi, in_openerr_string
  739.         jmp     in_err
  740. readerr:
  741.         mov     esi, readerr_string
  742. in_err:
  743.         call    sayerr
  744.         mov     al, 4
  745.         call    exit
  746.  
  747. out_openerr:
  748.         mov     esi, out_openerr_string
  749.         jmp     out_err
  750. writeerr:
  751.         mov     esi, writeerr_string
  752. out_err:
  753.         call    sayerr
  754.         mov     al, 5
  755.         call    exit
  756.  
  757. ; Platform-specific procedures.
  758. match =WINDOWS,OS { include 'windows_sys.inc' }
  759. match =LINUX,OS { include 'linux_sys.inc' }
  760.  
  761. ; Data
  762. macro string a, [b] {
  763. common
  764.         db      a ## _end - a
  765. a       db      b
  766. a ## _end:
  767. }
  768.  
  769. string information_string, 'Usage: fasmdep [-e] [<input.fas> [<output.Po>]]',10
  770. string badfile_string, 'Not .fas file',10
  771. string nomemory_string, 'No memory',10
  772. string in_openerr_string, 'Cannot open input file',10
  773. string readerr_string, 'Read error',10
  774. string out_openerr_string, 'Cannot create output file',10
  775. string writeerr_string, 'Write error',10
  776.  
  777. include_variable        db      'INCLUDE',0
  778. TokenOn         db      2,'on'
  779. TokenFrom       db      4,'from'
  780.