Compiles & runs with FreeBASIC 0.14b, probably works with other versions. Only minimally tested. :-)
Code:
Option Explicit
'$Include: 'win/kernel32.bi'
Declare Sub InitTable()
Declare Sub ProcessCommandLine()
Declare Sub ProcessCommandLineItem(item$)
Declare Sub RecursivelyFindFiles(base_path$, spec$)
Declare Sub RecursivelyFindSubdirs(base_path$, subdir_spec$, remaining_spec$)
Declare Sub ProcessFile(path_to_file$)
Declare Sub OutputStatistics(lines As LongInt, words As LongInt, characters As LongInt, label$)
Declare Function MinimumNonZero(a&, b&) As Long
Const FLAG_COUNT_LF_1% = 1
Const FLAG_COUNT_SPACE_1% = 2
Const FLAG_COUNT_CRLF_2% = 4
Const FLAG_COUNT_SPACE_2% = 8
Const FLAG_COUNT_CR_1% = 16
Const FLAG_ENABLE_NEXT_LF_1% = 32
Const FLAG_ENABLE_NEXT_SPACE_1% = 64
Const FLAG_ALWAYS_ON% = 128
Dim Shared table(0 To 65535) As UByte
Dim Shared As LongInt total_lines, total_words, total_characters
Dim Shared show_lines%, show_words%, show_characters%, num_files&
Const FILE_ATTR_DIRECTORY% = 16
Const FILE_ATTR_ARCHIVE% = 32
InitTable
ProcessCommandLine
Sub InitTable()
Dim i&, a$, byte1%, byte2%
For i& = 0 To 65535
table(i&) = FLAG_ALWAYS_ON% Or FLAG_ENABLE_NEXT_LF_1% Or FLAG_ENABLE_NEXT_SPACE_1%
a$ = MKI$(i&)
byte1% = Asc(a$)
byte2% = Asc(a$, 2)
Select Case byte1%
Case 13: table(i&) = table(i&) Or FLAG_COUNT_SPACE_1% Or FLAG_COUNT_CR_1%
Case 10: table(i&) = table(i&) Or FLAG_COUNT_SPACE_1% Or FLAG_COUNT_LF_1%
Case 32: table(i&) = table(i&) Or FLAG_COUNT_SPACE_1%
End Select
Select Case byte2%
Case 13:
table(i&) = table(i&) Or FLAG_COUNT_CRLF_2%
table(i&) = table(i&) And Not FLAG_ENABLE_NEXT_LF_1%
Case 10:
If byte1% <> 13 Then table(i&) = table(i&) Or FLAG_COUNT_CRLF_2%
End Select
Select Case byte2%
Case 13, 10, 32:
table(i&) = table(i&) And Not FLAG_ENABLE_NEXT_SPACE_1%
Select Case byte1%
Case 13, 10, 32: ' do nothing
Case Else:
table(i&) = table(i&) Or FLAG_COUNT_SPACE_2%
End Select
End Select
Next i&
End Sub
Sub ProcessCommandLine()
Dim show_all%
Dim cmd$, in_string%, i&, c%, word$, word_length%
show_all% = -1
show_lines% = 0
show_words% = 0
show_characters% = 0
in_string% = 0
cmd$ = Trim$(Command$)
While Asc(cmd$) = 45 ' the '-' character
show_all% = 0
For i& = 2 To Len(cmd$)
c% = Asc(cmd$, i&)
Select Case c%
Case 32:
cmd$ = LTrim$(Mid$(cmd$, i& + 1))
Exit For
Case 108: ' the 'l' character, for lines
show_lines% = -1
Case 119: ' the 'w' character, for words
show_words% = -1
Case 99: ' the 'c' character, for characters
show_characters% = -1
Case Else:
Print "Usage: wc [-lwc] [filename ...]"
End
End Select
Next i&
WEnd
If show_all% Then
show_lines% = -1
show_words% = -1
show_characters% = -1
End If
word$ = Space$(10)
word_length% = 0
For i& = 1 To Len(cmd$)
c% = Asc(cmd$, i&)
If in_string% Then
If c% = 34 Then
in_string% = 0
Else
word_length% = word_length% + 1
If word_length% > Len(word$) Then word$ = word$ + Space$(10)
Mid$(word$, word_length%, 1) = Chr$(c%)
End If
Else
If c% = 34 Then
in_string% = 0
ElseIf c% = 32 Then
If word_length% Then ProcessCommandLineItem Left$(word$, word_length%)
word_length% = 0
Else
word_length% = word_length% + 1
If word_length% > Len(word$) Then word$ = word$ + Space$(10)
Mid$(word$, word_length%, 1) = Chr$(c%)
End If
End If
Next i&
If word_length% Then ProcessCommandLineItem Left$(word$, word_length%)
If num_files& > 1 Then OutputStatistics total_lines, total_words, total_characters, "total"
End Sub
Sub ProcessCommandLineItem(item$)
Dim i&, c%
If Abs(Asc(item$, Len(item$)) * 2 - 139) = 45 Then ' fancy way of saying "is either '/' or '\'"
Exit Sub ' if the spec ends in a divider, then no files are named
End If
If Len(item$) > 3 Then
If (Asc(item$, 2) = 58) And Abs(Asc(item$, 3) * 2 - 139) = 45 Then
For i& = 4 To Len(item$)
c% = Asc(item$, i&)
If (c% <> 47) And (c% <> 92) Then
RecursivelyFindFiles Left$(item$, 2) + "\", Mid$(item$, i&)
Exit For
End If
Next i&
End If
Else
Select Case Asc(item$, 1)
Case 47, 92: ' the '/' and '\' characters, respectively
For i& = 2 To Len(item$)
c% = Asc(item$, i&)
If (c% <> 47) And (c% <> 92) Then
RecursivelyFindFiles "\", Mid$(item$, i&)
Exit For
End If
Next i&
Case Else:
RecursivelyFindFiles "", item$
End Select
End If
End Sub
Function MinimumNonZero(a&, b&) As Long
If a& = 0 Then
MinimumNonZero = b&
ElseIf b& = 0 Then
MinimumNonZero = a&
ElseIf a& < b& Then
MinimumNonZero = a&
Else
MinimumNonZero = b&
End If
End Function
Sub RecursivelyFindFiles(base_path$, spec$)
Dim divider&, this_spec$, next_spec$
divider& = MinimumNonZero(InStr(spec$, "/"), InStr(spec$, "\"))
If divider& > 0 Then
this_spec$ = Left$(spec$, divider& - 1)
While Abs(Asc(spec$, divider&) * 2 - 139) = 45 ' fancy way of saying "is either '/' or '\'"
divider& = divider& + 1 ' we know the spec doesn't end in a divider because ProcessCommandLineItem checks that
WEnd
next_spec$ = Mid$(spec$, divider&)
RecursivelyFindSubdirs base_path$, this_spec$, next_spec$
Else
Dim filename$, full_filename$
filename$ = Dir$(base_path$ + spec$, -1)
While Len(filename$) <> 0
full_filename$ = base_path$ + filename$
If (GetFileAttributes(full_filename$) And FILE_ATTR_DIRECTORY%) = 0 Then ProcessFile full_filename$
filename$ = Dir$("", -1)
WEnd
End If
End Sub
Sub RecursivelyFindSubdirs(base_path$, subdir_spec$, remaining_spec$)
Dim num_subdirs%, subdirname$, i&
ReDim subdirs$(10)
num_subdirs% = 0
subdirname$ = Dir$(base_path$ + subdir_spec$, -1)
While Len(subdirname$) <> 0
If (GetFileAttributes(base_path$ + subdirname$) And FILE_ATTR_DIRECTORY%) <> 0 Then
If (subdirname$ <> ".") And (subdirname$ <> "..") Then
num_subdirs% = num_subdirs% + 1
If num_subdirs% > UBound(subdirs$) Then ReDim Preserve subdirs$(num_subdirs% + 10)
subdirs$(num_subdirs%) = base_path$ + subdirname$ + "\"
End If
End If
subdirname$ = Dir$("", -1)
WEnd
For i& = 1 To num_subdirs%
RecursivelyFindFiles subdirs$(i&), remaining_spec$
Next i&
End Sub
Sub ProcessFile(path_to_file$)
Dim ff%, state%, remaining&, i&
Dim As LongInt file_lines, file_words, file_characters
Dim buffer As String * 32768
Dim buf_ptr As UShort Ptr
buf_ptr = VarPtr(buffer)
num_files& = num_files& + 1
ff% = FreeFile
state% = FLAG_ALWAYS_ON% Or FLAG_ENABLE_NEXT_LF_1% Or FLAG_ENABLE_NEXT_SPACE_1%
Open path_to_file$ For Binary As #ff%
remaining& = LOF(ff%)
file_characters = remaining&
total_characters = total_characters + file_characters
While remaining& > 32768
Get #ff%, , buffer
remaining& = remaining& - 32768
For i& = 0 To 16383
state% = ((state% SHR 5) Or &HF8) And table(buf_ptr[i&])
If state% And FLAG_COUNT_CR_1% Then
file_lines = file_lines + 1
total_lines = total_lines + 1
End If
If state% And FLAG_COUNT_LF_1% Then
file_lines = file_lines + 1
total_lines = total_lines + 1
End If
If state% And FLAG_COUNT_CRLF_2% Then
file_lines = file_lines + 1
total_lines = total_lines + 1
End If
If state% And FLAG_COUNT_SPACE_1% Then
file_words = file_words + 1
total_words = total_words + 1
End If
If state% And FLAG_COUNT_SPACE_2% Then
file_words = file_words + 1
total_words = total_words + 1
End If
Next i&
WEnd
Get #ff%, , buffer
If (remaining& And 1) <> 0 Then
buf_ptr[remaining& SHR 1] = buf_ptr[remaining& SHR 1] And &HFF
End If
For i& = 0 To (remaining& + 1) SHR 1
state% = ((state% SHR 5) Or &HF8) And table(buf_ptr[i&])
If (state% And FLAG_COUNT_CR_1%) <> 0 Then
file_lines = file_lines + 1
total_lines = total_lines + 1
End If
If (state% And FLAG_COUNT_LF_1%) <> 0 Then
file_lines = file_lines + 1
total_lines = total_lines + 1
End If
If (state% And FLAG_COUNT_CRLF_2%) <> 0 Then
file_lines = file_lines + 1
total_lines = total_lines + 1
End If
If (state% And FLAG_COUNT_SPACE_1%) <> 0 Then
file_words = file_words + 1
total_words = total_words + 1
End If
If (state% And FLAG_COUNT_SPACE_2%) <> 0 Then
file_words = file_words + 1
total_words = total_words + 1
End If
Next i&
If (state% And FLAG_ENABLE_NEXT_SPACE_1%) <> 0 Then
file_words = file_words + 1
total_words = total_words + 1
End If
Close #ff%
OutputStatistics file_lines, file_words, file_characters, path_to_file$
End Sub
Sub OutputStatistics(lines As LongInt, words As LongInt, characters As LongInt, label$)
If show_lines% Then Print Using "####### "; lines;
If show_words% Then Print Using "######## "; words;
If show_characters% Then Print Using "######### "; characters;
Print label$
End Sub