rubylexer 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -0
  2. data/History.txt +64 -0
  3. data/Makefile +2 -2
  4. data/README.txt +13 -9
  5. data/bin/rubylexer +113 -0
  6. data/lib/assert.rb +1 -1
  7. data/lib/rubylexer.rb +856 -305
  8. data/lib/rubylexer/charhandler.rb +1 -1
  9. data/lib/rubylexer/charset.rb +15 -7
  10. data/lib/rubylexer/context.rb +10 -2
  11. data/lib/rubylexer/lextable.rb +1 -0
  12. data/lib/rubylexer/rubycode.rb +1 -1
  13. data/lib/rubylexer/rulexer.rb +106 -32
  14. data/lib/rubylexer/symboltable.rb +1 -1
  15. data/lib/rubylexer/test/oneliners.rb +15 -5
  16. data/lib/rubylexer/test/oneliners_1.9.rb +116 -92
  17. data/lib/rubylexer/test/stanzas.rb +49 -27
  18. data/lib/rubylexer/test/testcases.rb +2 -2
  19. data/lib/rubylexer/token.rb +153 -23
  20. data/lib/rubylexer/tokenprinter.rb +9 -6
  21. data/lib/rubylexer/version.rb +1 -1
  22. data/rubylexer.gemspec +12 -8
  23. data/test/bad/ruby_lexer.rb +7 -0
  24. data/test/code/deletewarns.rb +1 -1
  25. data/test/code/dumptokens.rb +1 -81
  26. data/test/code/heredoc_blast_test.rb +112 -0
  27. data/test/code/locatetest.rb +1 -1
  28. data/test/code/regression.rb +23 -23
  29. data/test/code/rubylexervsruby.rb +59 -12
  30. data/test/code/tokentest.rb +62 -52
  31. data/test/data/23.rb +0 -1
  32. data/test/data/g.rb +0 -1
  33. data/test/data/heremonsters.rb +1 -1
  34. data/test/data/heremonsters_dos.rb +1 -1
  35. data/test/data/pre.rb +0 -1
  36. data/test/data/pre.unix.rb +0 -1
  37. data/test/data/putstext.rb +4 -0
  38. data/test/data/regtest.rb +0 -1
  39. data/test/data/stuffydog.rb +5 -0
  40. data/test/data/stuffydog2.rb +5 -0
  41. data/test/data/wsdlDriver.rb +0 -1
  42. data/test/test.sh +1 -1
  43. data/test/test_all.rb +3 -0
  44. data/test/test_bad_rubylexer.rb +16 -0
  45. data/test/test_rubylexer_bad.rb +12 -0
  46. data/testing.txt +40 -20
  47. metadata +51 -38
@@ -1,6 +1,6 @@
1
- =begin legal crap
1
+ =begin legalia
2
2
  rubylexer - a ruby lexer written in ruby
3
- Copyright (C) 2004,2005,2008 Caleb Clausen
3
+ Copyright (C) 2004,2005,2008, 2011 Caleb Clausen
4
4
 
5
5
  This library is free software; you can redistribute it and/or
6
6
  modify it under the terms of the GNU Lesser General Public
@@ -34,7 +34,7 @@ class SimpleTokenPrinter
34
34
  TOKENSPERLINE=8
35
35
  TOKENSMAGICMAP="\n"+' '*(TOKENSPERLINE-1)
36
36
 
37
- def pprint(tok,output=$stdout) output.print(sprint(tok)) end
37
+ def pprint(tok,output=$stdout) output<<(sprint(tok)) end
38
38
 
39
39
  def sprint(tok)
40
40
  case tok
@@ -73,9 +73,9 @@ end
73
73
  else
74
74
  ''
75
75
  end
76
- if ?= == @ident.to_s[0]
76
+ #if ?= == @ident.to_s[0]
77
77
  result+="\\\n"*@ident.to_s.scan(/\r\n?|\n\r?/).size
78
- end
78
+ #end
79
79
 
80
80
  return result
81
81
  end end
@@ -85,6 +85,9 @@ end
85
85
  class OutlinedHereBodyToken; def ws_munge(tp)
86
86
  nil
87
87
  end end
88
+ class EncodingDeclToken; def ws_munge(tp)
89
+ nil
90
+ end end
88
91
  class ZwToken; def ws_munge(tp)
89
92
  case tp.showzw
90
93
  when 2; explicit_form_all
@@ -117,7 +120,7 @@ class KeepWsTokenPrinter
117
120
  def pprint(tok,output=$stdout)
118
121
  @accum<<aprint(tok).to_s
119
122
  if (@accum.size>ACCUMSIZE and NewlineToken===tok) or EoiToken===tok
120
- output.print(@accum.join)
123
+ output<<(@accum.join)
121
124
  @accum=[]
122
125
  end
123
126
  end
@@ -1,3 +1,3 @@
1
1
  class RubyLexer
2
- VERSION='0.7.7'
2
+ VERSION='0.8.0'
3
3
  end
@@ -1,9 +1,12 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ dir=File.dirname(__FILE__)
3
+ require "#{dir}/lib/rubylexer/version"
4
+ RubyLexer::Description=open("#{dir}/README.txt"){|f|
5
+ f.read[/^==+ ?description[^\n]*?\n *\n?((?:(?!\n *\n).)*)\n *\n/im,1]
6
+ }
7
+ RubyLexer::Latest_changes="###"+open("#{dir}/History.txt"){|f| f.read[/\A===(.*?)(?====)/m,1] }
2
8
 
3
- require "#{File.dirname(__FILE__)}/lib/rubylexer/version"
4
- RubyLexer::Description=open("README.txt"){|f| f.read[/^==+ ?description[^\n]*?\n *\n?(.*?\n *\n.*?)\n *\n/im,1] }
5
- RubyLexer::Latest_changes="###"+open("History.txt"){|f| f.read[/\A===(.*?)(?====)/m,1] }
6
-
9
+ @@the_gem=
7
10
  Gem::Specification.new do |s|
8
11
  s.name = "rubylexer"
9
12
  s.version = RubyLexer::VERSION
@@ -15,9 +18,10 @@ Gem::Specification.new do |s|
15
18
  s.homepage = %{http://github.com/coatl/rubylexer}
16
19
  s.rubyforge_project = %q{rubylexer}
17
20
 
18
- s.files = `git ls-files`.split
21
+ s.files = `git ls-files`.split - ['.gitignore']
19
22
  s.test_files = %w[test/test_all.rb]
20
23
  s.require_paths = ["lib"]
24
+ s.bindir = "bin"
21
25
  s.extra_rdoc_files = ["README.txt", "COPYING"]
22
26
  s.has_rdoc = true
23
27
  s.rdoc_options = %w[--main README.txt]
@@ -30,11 +34,11 @@ Gem::Specification.new do |s|
30
34
  s.specification_version = 2
31
35
 
32
36
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
33
- s.add_runtime_dependency("sequence", [">= 0.2.3"])
37
+ s.add_runtime_dependency("sequence", [">= 0.2.4"])
34
38
  else
35
- s.add_dependency("sequence", [">= 0.2.3"])
39
+ s.add_dependency("sequence", [">= 0.2.4"])
36
40
  end
37
41
  else
38
- s.add_dependency("sequence", [">= 0.2.3"])
42
+ s.add_dependency("sequence", [">= 0.2.4"])
39
43
  end
40
44
  end
@@ -0,0 +1,7 @@
1
+ class RubyLexer
2
+ def initialize(*args)
3
+ p :the_wrong_rubylexer!
4
+ $the_wrong_rubylexer=1
5
+ end
6
+ end
7
+ $the_wrong_rubylexer=0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  =begin legal crap
3
3
  rubylexer - a ruby lexer written in ruby
4
- Copyright (C) 2004,2005,2008 Caleb Clausen
4
+ Copyright (C) 2004,2005,2008, 2011 Caleb Clausen
5
5
 
6
6
  This library is free software; you can redistribute it and/or
7
7
  modify it under the terms of the GNU Lesser General Public
@@ -1,81 +1 @@
1
- #!/usr/bin/env ruby
2
- =begin legal crap
3
- rubylexer - a ruby lexer written in ruby
4
- Copyright (C) 2004,2005,2008 Caleb Clausen
5
-
6
- This library is free software; you can redistribute it and/or
7
- modify it under the terms of the GNU Lesser General Public
8
- License as published by the Free Software Foundation; either
9
- version 2.1 of the License, or (at your option) any later version.
10
-
11
- This library is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
- Lesser General Public License for more details.
15
-
16
- You should have received a copy of the GNU Lesser General Public
17
- License along with this library; if not, write to the Free Software
18
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
- =end
20
-
21
- $Debug=true
22
- require 'rubylexer'
23
- require 'getoptlong'
24
-
25
- #def puts(x) end
26
-
27
- #a Token#inspect that omits the object id
28
- class RubyLexer
29
- class Token
30
- def strify
31
- [self.class.name[/[^:]+$/],": ",instance_variables.sort.collect{|v|
32
- [v,"=",instance_variable_get(v).inspect," "]
33
- }].join
34
- end
35
- end
36
- end
37
-
38
- name=macros=silent=file=nil
39
- options={}
40
- #allow -e
41
- opts=GetoptLong.new(
42
- ["--eval", "-e", GetoptLong::REQUIRED_ARGUMENT],
43
- ["--silent", "-s", GetoptLong::NO_ARGUMENT],
44
- ["--macro", "-m", GetoptLong::NO_ARGUMENT],
45
- ["--ruby19", "-9", GetoptLong::NO_ARGUMENT]
46
- )
47
- opts.each{|opt,arg|
48
- case opt
49
- when '--eval'
50
- file=arg
51
- name='-e'
52
- when '--silent'
53
- silent=true
54
- when '--macro'
55
- macros=true
56
- when '--ruby19'
57
- options[:rubyversion]=1.9
58
- end
59
- }
60
-
61
- #determine input file and its name if not already known
62
- file||=if name=ARGV.first
63
- File.open(name)
64
- else
65
- name='-'
66
- $stdin.read
67
- end
68
-
69
- args=name, file
70
- args.push 1,0,options unless options.empty?
71
- lexer=RubyLexer.new(*args)
72
- lexer.enable_macros! if macros
73
- if silent
74
- until RubyLexer::EoiToken===(tok=lexer.get1token)
75
- end
76
- else
77
- until RubyLexer::EoiToken===(tok=lexer.get1token)
78
- puts tok.strify
79
- end
80
- end
81
- puts tok.strify #print eoi token
1
+ load File.join(Dir.pwd,"bin/rubylexer")
@@ -0,0 +1,112 @@
1
+ require 'open3'
2
+
3
+ require 'rubygems'
4
+ require 'rubylexer/test/testcases'
5
+ require 'test/code/rubylexervsruby'
6
+ #require 'test/code/test_1.9'
7
+
8
+ SEP='';
9
+ 'caleb clausen'.each_byte{|ch| SEP<<ch.to_s(2).gsub('0','-').gsub('1','+')}
10
+ SEP<<'(0)'
11
+
12
+ #require 'test/code/rubyoracle'
13
+ class<<RubyLexerVsRuby
14
+
15
+ def progress ruby,input; end
16
+ =begin oracular version was just a bad idea....
17
+ def ruby_parsedump(input,output,ruby)
18
+ #todo: use ruby's md5 lib
19
+ #recursive ruby call here is unavoidable because -y flag has to be set
20
+
21
+ @oracle||= Open3.popen3("#{ruby} -w -y -c")
22
+ @oracle[0].write IO.read(input)
23
+ @oracle[0].flush
24
+ #timeout=Time.now+0.1
25
+ data=''
26
+ while data.empty? # and Time.now<timeout
27
+ begin
28
+ data<< chunk=@oracle[2].read_nonblock(1024)
29
+ timeout=Time.now+0.02
30
+ rescue EOFError
31
+ # data<<"\nError: premature eof...\n"
32
+ break
33
+ rescue Errno::EAGAIN
34
+ break if data[/^Reading a token: \Z/] and Time.now>=timeout
35
+ end while chunk
36
+ end
37
+
38
+ status=0
39
+ lines=data.split("\n")
40
+ File.open(output,"w") { |outfd|
41
+ lines.each{|line|
42
+ outfd.puts(line) if /^Shifting/===line
43
+ if /^#{DeleteWarns::WARNERRREX}|^Error|^(Now at end of input\.)/o===line
44
+ outfd.puts(line)
45
+ if status.zero? and $2!="warning"
46
+ status=1 unless $4 #unless end of input seen
47
+ @oracle.each{|fd| fd.close} if @oracle
48
+ @oracle=nil
49
+ end
50
+ end
51
+ }
52
+ }
53
+ return status
54
+ end
55
+ =end
56
+ end
57
+
58
+ require 'test/unit'
59
+
60
+ class LexerTests<Test::Unit::TestCase
61
+ class LexerTestFailure<RuntimeError; end
62
+ class DifferencesFromMRILex<LexerTestFailure; end
63
+
64
+ i=-1
65
+ test_code= TestCases::TESTCASES.grep(/insane/).map{|tc|
66
+ i+=1
67
+ name="testcase_#{i}__"
68
+ esctc=tc.gsub(/['\\]/){"\\"+$&}
69
+ %[
70
+ define_method '#{name}' do
71
+ 10_000.times do
72
+ lexer=RubyLexer.new('__#{name}','#{esctc}')
73
+ until RubyLexer::EoiToken===(token=lexer.get1token)
74
+ #...do stuff w/ token...
75
+ end
76
+ end
77
+ end
78
+ ]
79
+ }
80
+
81
+ illegal_test_code= TestCases::ILLEGAL_TESTCASES.map{|tc|
82
+ i+=1
83
+ name="testcase_#{i}__"
84
+ esctc=tc.gsub(/['\\]/){"\\"+$&}
85
+ %[
86
+ define_method '#{name}' do
87
+ difflines=[]
88
+ begin
89
+ res=RubyLexerVsRuby.rubylexervsruby('__#{name}','#{esctc}',difflines)
90
+ unless difflines.empty?
91
+ puts '#{esctc}'
92
+ puts difflines.join
93
+ raise DifferencesFromMRILex
94
+ end
95
+ res or raise LexerTestFailure, ''
96
+ rescue LexerTestFailure
97
+ puts 'warning: test failure lexing "#{esctc}"'
98
+ rescue Interrupt; exit
99
+ rescue Exception=>e;
100
+ message=e.message.dup<<"\n"+'while lexing: #{esctc}'
101
+ e2=e.class.new(message)
102
+ e2.set_backtrace(e.backtrace)
103
+ raise e2
104
+ end
105
+ end
106
+ ]
107
+ }
108
+
109
+ src=(test_code+illegal_test_code).join
110
+ # puts src
111
+ eval src
112
+ end
@@ -1,6 +1,6 @@
1
1
  =begin legal crap
2
2
  rubylexer - a ruby lexer written in ruby
3
- Copyright (C) 2004,2005,2008 Caleb Clausen
3
+ Copyright (C) 2004,2005,2008, 2011 Caleb Clausen
4
4
 
5
5
  This library is free software; you can redistribute it and/or
6
6
  modify it under the terms of the GNU Lesser General Public
@@ -60,32 +60,34 @@ end
60
60
  class LexerTests<Test::Unit::TestCase
61
61
  class LexerTestFailure<RuntimeError; end
62
62
  class DifferencesFromMRILex<LexerTestFailure; end
63
-
64
- i=-1
63
+
64
+ i=0
65
+
65
66
  test_code= TestCases::TESTCASES.map{|tc|
66
67
  i+=1
67
- name="testcase_#{i}__"
68
68
  esctc=tc.gsub(/['\\]/){"\\"+$&}
69
+ shorttc=esctc[0..200]
70
+ shorttc.chop! while shorttc[-1]==?\\
71
+ shorttc.gsub!("\0",'\\\\0')
72
+ name="test_lexing_of_#{shorttc}"
69
73
  %[
70
74
  define_method '#{name}' do
71
75
  difflines=[]
72
76
  begin
73
- res=RubyLexerVsRuby.rubylexervsruby('__#{name}','#{esctc}',difflines)
77
+ res=RubyLexerVsRuby.rubylexervsruby('__testcase_#{i}','#{esctc}',difflines)
74
78
  unless difflines.empty?
75
- puts '#{esctc}'
76
- puts difflines.join
77
- raise DifferencesFromMRILex
79
+ raise DifferencesFromMRILex, difflines.join
78
80
  end
79
81
  res or raise LexerTestFailure, ''
80
- rescue Interrupt; exit
81
- rescue Exception=>e
82
- message=e.message.dup<<"\n"+'while lexing: #{esctc}'
83
- e2=e.class.new(message)
84
- e2.set_backtrace(e.backtrace)
85
- raise e2
86
82
  end
87
83
  end
88
84
  ]
85
+ #rescue Interrupt; exit
86
+ #rescue Exception=>e
87
+ # message=e.message.dup<<"\n"+'while lexing: \#{esctc}'
88
+ # e2=e.class.new(message)
89
+ # e2.set_backtrace(e.backtrace)
90
+ # raise e2
89
91
  }
90
92
 
91
93
  illegal_test_code= TestCases::ILLEGAL_TESTCASES.map{|tc|
@@ -98,25 +100,23 @@ end
98
100
  begin
99
101
  res=RubyLexerVsRuby.rubylexervsruby('__#{name}','#{esctc}',difflines)
100
102
  unless difflines.empty?
101
- puts '#{esctc}'
102
- puts difflines.join
103
- raise DifferencesFromMRILex
103
+ raise DifferencesFromMRILex, difflines.join
104
104
  end
105
105
  res or raise LexerTestFailure, ''
106
106
  rescue LexerTestFailure
107
107
  puts 'warning: test failure lexing "#{esctc}"'
108
- rescue Interrupt; exit
109
- rescue Exception=>e;
110
- message=e.message.dup<<"\n"+'while lexing: #{esctc}'
111
- e2=e.class.new(message)
112
- e2.set_backtrace(e.backtrace)
113
- raise e2
114
108
  end
115
109
  end
116
110
  ]
111
+ #rescue Interrupt; exit
112
+ #rescue Exception=>e;
113
+ # message=e.message.dup<<"\n"+'while lexing: \#{esctc}'
114
+ # e2=e.class.new(message)
115
+ # e2.set_backtrace(e.backtrace)
116
+ # raise e2
117
117
  }
118
118
 
119
119
  src=(test_code+illegal_test_code).join
120
- # puts src
120
+ #puts src
121
121
  eval src
122
122
  end
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/ruby
2
- =begin legal crap
2
+ =begin legal
3
3
  rubylexer - a ruby lexer written in ruby
4
- Copyright (C) 2004,2005,2008 Caleb Clausen
4
+ Copyright (C) 2004,2005,2008, 2011 Caleb Clausen
5
5
 
6
6
  This library is free software; you can redistribute it and/or
7
7
  modify it under the terms of the GNU Lesser General Public
@@ -29,12 +29,43 @@ class<<RubyLexerVsRuby
29
29
  ENABLEMD5=false
30
30
  def nop_ruby(cmd,input,output,stringdata)
31
31
  # system %[echo "BEGIN{exit};">#{output}]
32
- File.open(output,'w'){|f| f.write "BEGIN{exit};\n" }
33
- if stringdata
34
- File.open(output,'a'){|f| f.write stringdata }
35
- else
36
- system [cmd,'"'+input+'"','>>',output].join(' ')
37
- end
32
+ File.open(output,'w'){|f|
33
+ if stringdata
34
+ stringdata=stringdata.dup
35
+ first=stringdata.slice! /\A.*\n?/
36
+ second=stringdata.slice! /\A.*\n?/
37
+ else
38
+ input=IO.popen %{#{cmd} "#{input}"}
39
+ first=input.readline
40
+ second=input.readline
41
+ stringdata=input.read
42
+ end
43
+ if first[0,2]=="#!"
44
+ if /\A\s*#.*coding/o===second
45
+ f.write first
46
+ f.write second
47
+ f.write "BEGIN{exit};\n"
48
+ f.write stringdata
49
+ else
50
+ f.write first
51
+ f.write "BEGIN{exit};\n"
52
+ f.write second
53
+ f.write stringdata
54
+ end
55
+ else
56
+ if /\A\s*#.*coding/o===first
57
+ f.write first
58
+ f.write "BEGIN{exit};\n"
59
+ f.write second
60
+ f.write stringdata
61
+ else
62
+ f.write "BEGIN{exit};\n"
63
+ f.write first
64
+ f.write second
65
+ f.write stringdata
66
+ end
67
+ end
68
+ }
38
69
  end
39
70
 
40
71
  def ruby_parsedump(input,output,ruby)
@@ -45,17 +76,28 @@ def ruby_parsedump(input,output,ruby)
45
76
  ENABLEMD5 and system "md5sum -c #{input}.md5 2>/dev/null" and return
46
77
 
47
78
  status=0
79
+ #lexing_of_class.String
80
+ if RUBY_VERSION>"1.8" #workaround for 1.9 crash ???!!!
81
+ warn "warning: dodging segfault in mri 1.9 by pushing work off to egrep(?!)" unless defined? $parsedump_19_crash_warned
82
+ $parsedump_19_crash_warned=true
83
+ system "#{ruby} -w -y < #{input} 2>&1 | egrep '^Shifting|^((Reading a token: )?-:([0-9]+): (warning|(syntax )?error)(: (.+))?)' > #{output}"
84
+ else
85
+
48
86
  IO.popen("#{ruby} -w -y < #{input} 2>&1"){ |pipe|
49
87
  File.open(output,"w") { |outfd|
50
88
  pipe.each{ |line|
51
89
  outfd.print(line) \
52
90
  if /^Shifting|^#{DeleteWarns::WARNERRREX}/o===line
91
+ #WARNERRREX='(?:Reading a token: )?-:(\d+): (warning|(?:syntax )?error)(?:: ([^\n]+))?'
53
92
  #elsif /(warning|error)/i===line
54
93
  # raise("a warning or error, appearently, not caught by rex above: "+line)
55
94
  }
56
- pid,status=Process.waitpid2 pipe.pid #get err status of subprocess
95
+ #pid,status=Process.waitpid2 pipe.pid #get err status of subprocess
57
96
  }
58
97
  }
98
+ end
99
+
100
+ status=$?
59
101
  ENABLEMD5 and status==0 and system "md5sum #{input} > #{input}.md5" #compute sum only if no errors
60
102
  return status>>8
61
103
  end
@@ -92,7 +134,8 @@ expected_failures=Dir.getwd+"/test/code/"+File.basename(input)+".expected_failur
92
134
  nop_ruby "#{input[/\.gz$/]&&'z'}cat", input, nopfile, stringdata
93
135
 
94
136
 
95
- legal=ruby_parsedump nopfile, origfile, ruby
137
+ ruby_parsedump nopfile, origfile, ruby
138
+ `#{ruby} -c #{nopfile} >/dev/null 2>/dev/null`; legal=$?.to_i
96
139
  if legal.nonzero?
97
140
  puts "skipping #{input}; not legal"
98
141
  return true
@@ -106,11 +149,14 @@ rescue Exception=>rl_oops
106
149
  end
107
150
 
108
151
  begin
109
- raise unless 0==ruby_parsedump( _ttfile, p_ttfile, ruby )
110
- raise unless 0==ruby_parsedump( mttfile, pmttfile, ruby )
152
+ p_tt_fail=true unless 0==ruby_parsedump( _ttfile, p_ttfile, ruby )
153
+ pmtt_fail=true unless 0==ruby_parsedump( mttfile, pmttfile, ruby )
111
154
  rescue Exception=>ru_oops
112
155
  end
113
156
 
157
+ warn "syntax error parsing #{pmttfile}" if pmtt_fail
158
+ warn "syntax error parsing #{p_ttfile}" if p_tt_fail
159
+
114
160
  if rl_oops
115
161
  if ru_oops
116
162
  #good, ignore it
@@ -123,6 +169,7 @@ elsif ru_oops
123
169
  return true
124
170
  end
125
171
 
172
+
126
173
  if File.exists?(p_ttfile)
127
174
  IO.popen("diff --unified=1 -b #{origfile} #{p_ttfile}"){ |pipe|
128
175
  File.open(p_ttdiff,"w") { |diff|