Skip to content

Commit 9e259b6

Browse files
committed
Merge branch 'patch'
# Conflicts: # xtra/DIFF.LUA
2 parents 3f656ab + ef5e06f commit 9e259b6

File tree

3 files changed

+64
-158
lines changed

3 files changed

+64
-158
lines changed

test/DIFF/README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ The script performs the following steps:
4343
- The function `diff_u` performs the actual comparison:
4444
- It reads two files line by line.
4545
- It tracks which lines are added, removed,
46-
or unchanged between the two files using the Myers diff algorithm
46+
or unchanged between the two files using a full matrix diff algorithm
4747
- It groups changes in "hunks,"
4848
which are logical sections of differences,
4949
and formats these differences in the unified diff style.
@@ -67,12 +67,12 @@ The script performs the following steps:
6767

6868
## Functions
6969

70-
| Function | Parameters | Description |
71-
|----------|-------------------------------------------------|-----------------------------------------------------------------------------|
72-
| `diff_u` | **f**ile**n**ame**1**<br/>**f**ile**n**ame**2** | Prints differences between the two files in to stdout. |
73-
| `fp` | | **F**lushes **p**re-context lines into the buffer when changes are detected |
74-
| `fh` | | **F**lushes a complete **h**unk of changes into the buffer |
75-
| `fb` | | **F**lush (print) and clear the **b**uffer |
76-
| `di` | | **D**iagonally **i**terate (lines that match in both files) |
77-
| `ri` | | **Ri**ght iterate (for a line only present in the old file) |
78-
| `dn` | | **D**ow**n** Iterate (for a line only present in the new file) |
70+
| Function | Parameters | Description |
71+
|----------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
72+
| `diff_u` | **f**ile**n**ame**1**<br/>**f**ile**n**ame**2** | Prints differences between the two files in to stdout. |
73+
| `cmp` | x</br>y | **C**o**mp**ares line `x` in the old file with line `y` in the new file |
74+
| `get` | filename<br>packedIdx<br>idx | Open seek and read line `idx` of `filename` |
75+
| `pfl` | marker<br>line | **F**ormats a single diff **l**ine by **p**refixing `marker` and stripping newlines |
76+
| `init` | | Begins a new hunk: collects up to `DIFF_CONTEXT` lines of unchanged context before the first change in a hunk. |
77+
| `flush` | | Emits the current hunk buffer to stdout once either enough changes have accumulated or the hunk is closed. Prepends the unified-diff hunk header. |
78+
| `nlc` | | After finishing a file, checks if the last line lacked a newline and, if so, inserts a `\ No newline at end of file` marker into the current hunk. |

xtra/DIFF.LUA

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env lua
22

3-
local PF,CT,M,S="I2I4I8",{},0xFFFFFFFF,"set"
3+
local PF,CT,M,S,CL="I2I4I8",{},0xFFFFFFFF,"set",tonumber(os.getenv("DIFF_CONTEXT"))or 3
44
local function diff_u(fn1,fn2)
55
local function open(fn)local f,e=io.open(fn,"rb")if not f then print(e)os.exit(1)end
66
local function crc32(s)local c=M for i=1,#s do local byte=s:byte(i)c=(c >> 8)~CT[(c~byte)&0xFF]end return(~c)&M end
@@ -9,50 +9,32 @@ local function diff_u(fn1,fn2)
99
local function eol()if c=='\r'then local d=f:read(1)if not d then return true end f:seek("cur",-1)return d~='\n'end return c=='\n'end
1010
o=o+1 table.insert(l,c)if eol()then break end end
1111
if #l==0 then break end l=table.concat(l)local ln,c=#l,crc32(l)p=p..string.pack(PF,ln,c,ls)end f:close()return p end
12-
local f1,f2,ps,i,j,la,fh,h,w,m,u=open(fn1),open(fn2),string.packsize(PF),1,1
12+
local f1,f2,ps,co,i,j,la,fh,h,w,m,u,L=open(fn1),open(fn2),string.packsize(PF),{}
1313
local function cmp(x,y)
1414
local l1,c1,o1,l2,c2,o2,b1,e,b2,f=string.unpack(PF,f1,(x-1)*ps+1)l2,c2,o2=string.unpack(PF,f2,(y-1)*ps+1)if l1~=l2 or c1~=c2 then return false end
1515
local function li(fn,o,l)f,e=io.open(fn,"rb")if not f then return nil,e end f:seek(S,o)local g=f:read(l)f:close()return g end
1616
b1,e,b2=li(fn1,o1,l1)if not b1 then error(e)end b2,e=li(fn2,o2,l2)if not b2 then error(e)end return b1==b2 end
17-
h,w,m=#f1//ps,#f2//ps,{}
18-
local ly,l,n=1
19-
for x=1,h do
20-
local b={}
21-
local function inList(v)for k,p in ipairs(b)do if v==p then return k end end end
22-
for y=ly,w do if cmp(x,y)then table.insert(b,y)end end
23-
if #b>0 then
24-
if #m>0 then
25-
l=m[#m]
26-
if l.x+l.l==x then
27-
n=inList(l.y+l.l)
28-
if n then l.l=l.l+1 end
29-
else
30-
--TODO: calculate how far ahead we should be from last and current x and y
31-
n=inList(l.y+l.l)
32-
if n then table.insert(m,{y=b[n],x=x,l=1}) end
33-
end
34-
else
35-
table.insert(m,{y=b[1],x=x,l=1})
36-
end
37-
end
38-
end
17+
h,w,la,m,L=#f1//ps,#f2//ps,1,{},{} for x=0,h do L[x]={}for y=0,w do if x==0 or y==0 then L[x][y]=0
18+
elseif cmp(x,y)then L[x][y]=L[x-1][y-1]+1
19+
else L[x][y]=(L[x-1][y]>L[x][y-1])and L[x-1][y]or L[x][y-1]end end end
20+
i,j=h,w while i > 0 and j > 0 do
21+
if cmp(i,j)then table.insert(m,1,{x=i,y=j,l=1})i,j=i-1,j-1
22+
elseif L[i-1][j]>=L[i][j-1]then i=i-1 else j=j-1 end end
23+
for _,p in ipairs(m)do local t=co[#co] if t and t.x+t.l==p.x and t.y+t.l==p.y then t.l=t.l+1 else co[#co+1]={x=p.x,y=p.y,l=p.l}end end
24+
m,la,i,j=co,nil,1,1
3925
local function get(fn,p,x)local l,_,o,f,r=string.unpack(PF,p,(x-1)*ps+1)f=assert(io.open(fn,"rb"))f:seek(S,o)r=f:read(l)f:close()return r end
4026
local function pfl(p,s)return(p..s:gsub('\n$',''):gsub('\r$',''))end
41-
local function flush()if #u.l>0 then if u.e>=4 then table.remove(u.l)u.o.l,u.n.l=u.o.l-1,u.n.l-1 end
27+
local function flush()if #u.l>0 then if u.e>CL then table.remove(u.l)u.o.l,u.n.l=u.o.l-1,u.n.l-1 end
4228
table.insert(u.l,1,"@@ -"..u.o.s..(u.o.l==1 and""or","..u.o.l).." +"..u.n.s..(u.n.l==1 and""or","..u.n.l).. " @@")
4329
if not fh then local function fn(s)return string.match(s,"%s")and'"'..s..'"'or s end table.insert(u.l,1,"+++ "..fn(fn2))table.insert(u.l,1,"--- "..fn(fn1))fh=true end
4430
for _,v in ipairs(u.l)do print(v)end end u=nil end
4531
local function init()if not u then local k,l=math.max(i-3,1),math.max(j-3,1)u={e=0,l={},o={s=k,l=0},n={s=l,l=0}}
4632
for z=k,i-1 do table.insert(u.l,pfl(" ",get(fn1,f1,z)))u.o.l,u.n.l=u.o.l+1,u.n.l+1 end end end
47-
--TODO: Select correct y grid on each compare table
48-
print("x","y","l")
4933
for _,p in ipairs(m)do
50-
-- Temporary debug code
51-
print(p.x, p.y, p.l)--[[
5234
while i<p.x do init()table.insert(u.l,pfl("-",get(fn1,f1,i)))i,u.o.l,u.e=i+1,u.o.l+1,0 end
5335
while j<p.y do init()table.insert(u.l,pfl("+",get(fn2,f2,j)))j,u.n.l,u.e=j+1,u.n.l+1,0 end
54-
if u then if u.e<4 then table.insert(u.l,pfl(" ",get(fn1,f1,i)))u.e,u.o.l,u.n.l=u.e+1,u.o.l+1,u.n.l+1 else flush()end end i,j=i+1,j+1 end
55-
--]]end os.exit(1)
36+
if u then local c=p.l>CL+2 and CL+1 or p.l while u.e<c and i<=h and j<=w do table.insert(u.l,pfl(" ",get(fn1,f1,i)))i,j,u.e,u.o.l,u.n.l=i+1,j+1,u.e+1,u.o.l+1,u.n.l+1 end if p.l>5 then flush() end end
37+
i,j=p.x+p.l,p.y+p.l end
5638
local function nlc()if la then la=la:sub(-1)if la~='\n'and la~='\r'then table.insert(u.l,"\\ No newline at end of file")end end end
5739
while i<=h do init()la=get(fn1,f1,i,ps)table.insert(u.l,pfl("-",la))i,u.o.l,u.e=i+1,u.o.l+1,0 end nlc()
5840
while j<=w do init()la=get(fn2,f2,j,ps)table.insert(u.l,pfl("+",la))j,u.n.l,u.e=j+1,u.n.l+1,0 end nlc()

xtra/PATCH.LUA

Lines changed: 41 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,45 @@
11
#!/usr/bin/env lua
22

3+
M="malformed"
34
local function patch(p)
4-
local pf, e = io.open(p)
5-
local ol, bl, cp, of, bf, hunk = 0, 0
6-
if not pf then print(e) os.exit(1) end
7-
8-
local function parseFileName(l)
9-
local first = l:sub(5,5)
10-
if first == '"' or first == "'" then
11-
return l:match(first .. "([^" .. first .. "]+)" .. first, 5)
12-
else
13-
local words = {}
14-
for v in string.gmatch(l, "%S+") do table.insert(words, v) end
15-
return words[2]
16-
end
17-
end
18-
5+
local pf,e=io.open(p)local ol,bl,cp,of,bf,h=0,0
6+
if not pf then print(e)os.exit(1)end
7+
local function parseFileName(l)local first=l:sub(5,5)
8+
if first=='"'or first=="'"then return l:match(first.."([^"..first.."]+)"..first,5)
9+
else local words={}for v in string.gmatch(l,"%S+")do table.insert(words,v)end return words[2]end end
1910
local function setPatchFile(f)
20-
local o, b
21-
local function abort()
22-
print(e)
23-
if o then o:close() end
24-
if b then b:close() end
25-
return false
26-
end
27-
o, e = io.open(f)if not o then return abort end
28-
b, e = io.open(f..".orig", "w")if not b then return abort end
29-
for m in o:lines() do b:write(m..'\n') end o:close() b:close()
30-
o, e = io.open(f, "w") if not o then return abort end
31-
b, e = io.open(f..".orig") if not b then return abort end
32-
if of then of:close() end if bf then bf:close() end of = o bf = b
33-
return true
34-
end
35-
36-
local function writeRemainder()
37-
if of then
38-
local rest = bf:read("*l")
39-
while rest do
40-
of:write(rest .. '\n')
41-
rest = bf:read("*l")
42-
end
43-
of:close()
44-
end
45-
bf:close()
46-
end
47-
48-
for l in pf:lines() do
49-
local ls = l:sub(1, 1)
50-
if ls == "+" then
51-
if l:sub(2,4) == "++ " then
52-
--TODO: Just ignore this line?
53-
else
54-
if of then of:write(l:sub(2) .. '\n') ol = ol + 1 end
55-
end
56-
elseif ls == '-' then
57-
if l:sub(2,4) == "-- " then
58-
local filename = parseFileName(l)
59-
if bf then writeRemainder() end
60-
if setPatchFile(filename) then cp = filename print("\n'" .. cp .. "':") end
61-
else
62-
local cmp = bf:read("*l") bl = bl + 1
63-
if cmp ~= l:sub(2) then print("malformed")os.exit(1) end
64-
end
65-
elseif ls == ' ' then
66-
--TODO: context line verification
67-
local line = l:sub(2)
68-
if bf then
69-
local cmp = bf:read("*l") bl = bl + 1
70-
if cmp == line then
71-
if of then of:write(line .. '\n') ol = ol + 1 end
72-
else
73-
print("malformed")os.exit(1)
74-
end
75-
end
76-
77-
elseif ls == '@' then
78-
if l:sub(2,3) == "@ " then
79-
local function parseHunkHeader()
80-
local function split(i,d)
81-
local r={}
82-
for m in (i..d):gmatch("(.-)"..d)do table.insert(r,m)end
83-
return r
84-
end
85-
local words,old,new=split(l," "),{},{}
86-
if words[2] then old=split(words[2],",")end
87-
if words[3] then new=split(words[3],",")end
88-
if old[1] then old[1]=old[1]:sub(2)end
89-
if new[1] then new[1]=new[1]:sub(2)end
90-
return {
91-
old = { start = tonumber(old[1]), lines = tonumber(old[2]) or 1 },
92-
new = { start = tonumber(new[1]), lines = tonumber(new[2]) or 1 }
93-
}
94-
end
95-
if hunk then
96-
if bl - hunk.old.start ~= hunk.old.lines or ol - hunk.new.start ~= hunk.new.lines then
97-
print("malformed")os.exit(1)
98-
else
99-
print("patched")
100-
end
101-
end
102-
hunk=parseHunkHeader()
103-
if hunk then
104-
io.write("\t@@ -" .. hunk.old.start .. "," .. hunk.old.lines .. " +" .. hunk.new.start .. "," .. hunk.new.lines .. " @@: ")
105-
local skip, line = hunk.old.start > 0 and hunk.old.start - 1 or 0
106-
if hunk.new.start - 1 ~= skip then print("malformed")os.exit(1) end
107-
while bl < skip do
108-
line, bl = bf:read("*l"), bl + 1
109-
if of then of:write(line .. '\n') ol = ol + 1 end
110-
end
111-
end
112-
end
113-
end
114-
end
115-
pf:close()
116-
if hunk then print("patched") end
117-
if bf then writeRemainder() end
118-
end
119-
120-
if #arg < 1 then os.exit(1) end
121-
patch(arg[1], arg[2])
11+
local o,b local function abort()print(e)if o then o:close()end if b then b:close()end return false end
12+
o,e=io.open(f)if not o then return abort end
13+
b,e=io.open(f..".orig","w")if not b then return abort end
14+
for m in o:lines()do b:write(m..'\n')end o:close()b:close()
15+
o,e=io.open(f,"w")if not o then return abort end
16+
b,e=io.open(f..".orig")if not b then return abort end
17+
if of then of:close()end if bf then bf:close()end of=o bf=b return true end
18+
local function writeRemainder()if of then local rest=bf:read("*l")while rest do of:write(rest..'\n')rest=bf:read("*l")end of:close()end bf:close()end
19+
local n=pf:read("*l")while n do
20+
local l,ls=n n=pf:read("*l")ls=l:sub(1,1)
21+
if ls=='+'then
22+
if l:sub(2,4)=="++ " then --ignore
23+
else if of then local nl=n and n:match("^\\ No newline at end of file")of:write(l:sub(2)..(nl and""or'\n'))ol=ol + 1 end end
24+
elseif ls=='-'then
25+
if l:sub(2,4)=="-- " then local filename=parseFileName(l)if bf then writeRemainder()end
26+
if setPatchFile(filename)then cp=filename print("\n'"..cp.."':")end
27+
else local cmp=bf:read("*l")bl=bl + 1 if cmp ~=l:sub(2)then print(M)os.exit(1)end end
28+
elseif ls==' 'then
29+
if bf then local cmp,line=bf:read("*l"),l:sub(2)bl=bl + 1 if cmp==line then if of then of:write(line..'\n')ol=ol + 1 end else print(M)os.exit(1)end end
30+
elseif ls=='@'then if l:sub(2,3)=="@ " then
31+
local function parseHunkHeader()
32+
local function split(i,d)local r={}for m in (i..d):gmatch("(.-)"..d)do table.insert(r,m)end return r end
33+
local words,o,n=split(l," "),{},{}
34+
if words[2]then o=split(words[2],",")end if words[3]then n=split(words[3],",")end
35+
if o[1]then o[1]=o[1]:sub(2)end if n[1]then n[1]=n[1]:sub(2)end
36+
return{o={s=tonumber(o[1]),l=tonumber(o[2])or 1},n={s=tonumber(n[1]),l=tonumber(n[2])or 1}}end
37+
if h then if h.o.l~=bl-(h.o.s-1)or h.n.l~=ol-(h.n.s-1)then print(M)os.exit(1)else print("patched")end end
38+
h=parseHunkHeader()if h then
39+
io.write("\t@@ -"..h.o.s..","..h.o.l.." +"..h.n.s..","..h.n.l.." @@: ")
40+
local s=h.o.s>0 and(h.o.s-1)-bl or 0 if s<0 then print(M)os.exit(1)end
41+
for _=1,s do
42+
local ctx=bf:read("*l")if not ctx then print(M)os.exit(1)end
43+
bl=bl+1 if of then of:write(ctx.."\n")ol=ol+1 end end end end end end
44+
pf:close()if h then print("patched")end if bf then writeRemainder()end end
45+
if #arg<1 then os.exit(1)end patch(arg[1])

0 commit comments

Comments
 (0)