# How much of SchOS is actually SchOS. # # `wc -l` over the whole tree answers a question nobody asked: it counts musl, # toybox, mksh and a NetBSD checkout, which between them are # two orders of magnitude larger than this project and none of them ours. This # counts only first-party code, and separates the code from the comments so the # number means something. # # tools/loc.ps1 the summary table # tools/loc.ps1 -PerFile every file, largest first # tools/loc.ps1 -All include third-party trees, for comparison # # Comment detection is deliberately simple: it handles // and /* */ for C and # # for scripts, and does not try to understand a // inside a string literal. # That is worth being honest about -- the comment ratio is indicative, not # exact. The line and byte counts are exact. param( [switch]$PerFile, [switch]$All, [int]$Top = 30 ) $ErrorActionPreference = 'Stop' $root = Split-Path $PSScriptRoot -Parent # What counts as ours. Everything else in the tree is either somebody else's # source or generated output, and mixing either in makes the total meaningless. $areas = [ordered]@{ 'kernel' = 'kernel' 'include' = 'include' 'boot' = 'boot' 'lib' = 'lib' 'cmd' = 'cmd' 'tools' = 'tools' 'docs' = 'docs' 'projects' = 'projects' } if ($All) { $areas['ports (3rd party)'] = 'ports' $areas['src-trunk (NetBSD)'] = 'src-trunk' } # Source only. Images, fonts-as-data and upstream tarballs are not code. $exts = @('.c', '.h', '.S', '.s', '.ld', '.inc', '.ps1', '.sh', '.pl', '.py', '.md', '.mk') function Measure-Area([string]$rel) { $dir = Join-Path $root $rel $res = [PSCustomObject]@{ Area = $rel; Files = 0; Lines = 0; Code = 0; Comment = 0; Blank = 0 Bytes = 0; NonWs = 0; Alpha = 0; Digit = 0; Files_ = @() } if (-not (Test-Path $dir)) { return $res } Get-ChildItem -Path $dir -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object { $f = $_ if ($exts -notcontains $f.Extension.ToLower()) { return } # Makefiles have no extension; picked up separately below. $text = [System.IO.File]::ReadAllText($f.FullName) $lines = $text -split "`r?`n" # A trailing newline produces one empty element; do not count it. if ($lines.Length -gt 0 -and $lines[-1] -eq '') { $lines = $lines[0..($lines.Length-2)] } $code = 0; $comment = 0; $blank = 0 $inBlock = $false $isC = @('.c','.h','.s','.ld','.inc') -contains $f.Extension.ToLower() foreach ($l in $lines) { $t = $l.Trim() if ($t -eq '') { $blank++; continue } if ($inBlock) { $comment++ if ($t -match '\*/') { $inBlock = $false } continue } if ($isC) { if ($t.StartsWith('//')) { $comment++; continue } if ($t.StartsWith('/*')) { $comment++ if ($t -notmatch '\*/') { $inBlock = $true } continue } } elseif ($t.StartsWith('#') -and $f.Extension -ne '.md') { $comment++; continue } $code++ } $nonWs = ($text.ToCharArray() | Where-Object { -not [char]::IsWhiteSpace($_) }).Count $alpha = ($text.ToCharArray() | Where-Object { [char]::IsLetter($_) }).Count $digit = ($text.ToCharArray() | Where-Object { [char]::IsDigit($_) }).Count $res.Files++ $res.Lines += $lines.Length $res.Code += $code $res.Comment += $comment $res.Blank += $blank $res.Bytes += $f.Length $res.NonWs += $nonWs $res.Alpha += $alpha $res.Digit += $digit $res.Files_ += [PSCustomObject]@{ Path = $f.FullName.Substring($root.Length + 1) Lines = $lines.Length; Code = $code; Comment = $comment } } return $res } $results = @() foreach ($k in $areas.Keys) { $results += (Measure-Area $areas[$k]) } # The top-level Makefile has no extension, so it is added by hand rather than # widening the extension list and dragging in every stray file in the tree. $mk = Join-Path $root 'Makefile' if (Test-Path $mk) { $mkLines = ([System.IO.File]::ReadAllText($mk) -split "`r?`n") $r = $results | Where-Object { $_.Area -eq 'tools' } | Select-Object -First 1 if ($r) { $r.Files++; $r.Lines += $mkLines.Length; $r.Bytes += (Get-Item $mk).Length $r.Code += ($mkLines | Where-Object { $_.Trim() -ne '' -and -not $_.Trim().StartsWith('#') }).Count $r.Comment += ($mkLines | Where-Object { $_.Trim().StartsWith('#') }).Count $r.Blank += ($mkLines | Where-Object { $_.Trim() -eq '' }).Count } } $fmt = @( @{ N='Area'; E={$_.Area} } @{ N='Files'; E={$_.Files}; A='right' } @{ N='Lines'; E={'{0:N0}' -f $_.Lines}; A='right' } @{ N='Code'; E={'{0:N0}' -f $_.Code}; A='right' } @{ N='Comment'; E={'{0:N0}' -f $_.Comment}; A='right' } @{ N='Blank'; E={'{0:N0}' -f $_.Blank}; A='right' } @{ N='Cmt%'; E={ if ($_.Code + $_.Comment) { '{0:N0}%' -f (100 * $_.Comment / ($_.Code + $_.Comment)) } else { '-' } }; A='right' } @{ N='Ln/File'; E={ if ($_.Files) { '{0:N0}' -f ($_.Lines / $_.Files) } else { '-' } }; A='right' } @{ N='KiB'; E={'{0:N0}' -f ($_.Bytes / 1KB)}; A='right' } ) Write-Host '' Write-Host ' SchOS — first-party source' -ForegroundColor Cyan Write-Host '' Write-Host (($results | Where-Object { $_.Files -gt 0 } | Sort-Object Lines -Descending | Format-Table $fmt -AutoSize | Out-String)) # Two totals, because they answer different questions and conflating them # flatters the number. "The OS" is what boots and runs on the machine; docs, # host-side tools and the projects/ stubs are first-party but are not SchOS # running on hardware. Linux 0.01's 10,239 lines were kernel, headers and boot # -- no documentation and no build tooling -- so only the first total is a fair # comparison. $osAreas = @('kernel','include','boot','lib','cmd') $own = $results | Where-Object { $_.Area -notin @('ports','src-trunk') } $os = $results | Where-Object { $_.Area -in $osAreas } $T = ($own | Measure-Object -Property Lines -Sum).Sum $C = ($own | Measure-Object -Property Code -Sum).Sum $M = ($own | Measure-Object -Property Comment -Sum).Sum $B = ($own | Measure-Object -Property Blank -Sum).Sum $F = ($own | Measure-Object -Property Files -Sum).Sum $By = ($own | Measure-Object -Property Bytes -Sum).Sum $NW = ($own | Measure-Object -Property NonWs -Sum).Sum $AL = ($own | Measure-Object -Property Alpha -Sum).Sum $DG = ($own | Measure-Object -Property Digit -Sum).Sum Write-Host (' {0,-22} {1,12:N0}' -f 'Files', $F) Write-Host (' {0,-22} {1,12:N0}' -f 'Lines total', $T) Write-Host (' {0,-22} {1,12:N0} {2:N0}%' -f ' of which code', $C, (100 * $C / [Math]::Max($T,1))) Write-Host (' {0,-22} {1,12:N0} {2:N0}%' -f ' of which comment', $M, (100 * $M / [Math]::Max($T,1))) Write-Host (' {0,-22} {1,12:N0} {2:N0}%' -f ' of which blank', $B, (100 * $B / [Math]::Max($T,1))) Write-Host (' {0,-22} {1,12:N0}' -f 'Lines per file', ($T / [Math]::Max($F,1))) Write-Host '' $osT = ($os | Measure-Object -Property Lines -Sum).Sum $osC = ($os | Measure-Object -Property Code -Sum).Sum $osF = ($os | Measure-Object -Property Files -Sum).Sum Write-Host (' {0,-22} {1,12:N0} ({2:N0} code, {3} files)' -f 'THE OS ITSELF', $osT, $osC, $osF) -ForegroundColor Green Write-Host (' {0,-22}' -f ' kernel+include+boot+lib+cmd') -ForegroundColor DarkGray Write-Host '' Write-Host (' {0,-22} {1,12:N0}' -f 'Bytes', $By) Write-Host (' {0,-22} {1,12:N0}' -f 'Non-whitespace chars', $NW) Write-Host (' {0,-22} {1,12:N0}' -f ' letters', $AL) Write-Host (' {0,-22} {1,12:N0}' -f ' digits', $DG) Write-Host '' # For scale. Linux 0.01 (September 1991) is 10,239 lines by the same crude # measure -- every line of every file in the tarball, comments included. $lin = 10239 Write-Host (' For scale: Linux 0.01 was {0:N0} lines (kernel, headers, boot -- the' -f $lin) -ForegroundColor DarkGray Write-Host (' same things counted above as THE OS ITSELF). SchOS is {0:N1}x that.' -f ($osT / $lin)) -ForegroundColor DarkGray Write-Host '' if ($PerFile) { Write-Host ' Largest files' -ForegroundColor Cyan # Out-String, not a bare Format-Table: two Format-Table calls writing to # the same output stream interleave their grouping records and PowerShell # rejects the second one outright. $rows = ($own | ForEach-Object { $_.Files_ }) | Sort-Object Lines -Descending | Select-Object -First $Top | Format-Table @{N='Lines';E={'{0:N0}' -f $_.Lines};A='right'}, @{N='Code'; E={'{0:N0}' -f $_.Code};A='right'}, @{N='Cmt'; E={'{0:N0}' -f $_.Comment};A='right'}, Path -AutoSize | Out-String Write-Host $rows }