SchOS/tools/loc.ps1
Nexoniarz e04ca28812 Rename Schunix to SchOS; the kernel becomes SchIFT
Removes the "-unix" element and with it any trademark exposure to The
Open Group.

The split follows the ecosystem layout: SchOS is the system, and the
kernel has its own name. Kernel-identity strings are the only places
that take SchIFT -- uname(2) sysname, /proc/version, and the two licence
sentences that say "kernel" outright. That is what every Unix does here:
`uname -s` on Linux reports the kernel, not the distribution. Everything
a user reads as the product -- panic screen, boot menu, error prefixes,
docs, LICENSE, NOTICE -- is SchOS.

schunix.img is schos.img, the logo assets and the musl TLS patch are
renamed, and __SCHUNIX__ is __SCHOS__ (the define and the patch that
consumes it moved together).

Two things the rename broke, both found by the bootcheck suite:

  - shmtest wrote "SCHUNIX-MMAP-OK" with a hardcoded length of 15 and
    compared it back by hand-counted offsets (m[3]=='U', m[14]=='K').
    The shorter marker left the write running two bytes past the literal
    and the compare reading past the data, failing a file-backed mmap
    that was working correctly. Now written and compared through one
    constant with sizeof.
  - bootcheck asserted a 31-byte file after appending to
    "hello-from-schunix"; the shorter string makes it 29.

Both are the same mistake: a string literal whose length is written out
somewhere else. Full suite green -- base, Tty, Ps, Sock, Fb, Usb, Evdev.
2026-08-09 14:09:59 +02:00

200 lines
8.8 KiB
PowerShell

# 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
}