精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

從Java走進Scala:構建計算器 case類和模式匹配

開發 后端
特定于領域的語言已經成為一個熱門話題;很多函數性語言之所以受歡迎,主要是因為它們可以用于構建特定于領域的語言。鑒于此,本文將著手構建一個簡單的計算器 DSL,以此來展示函數性語言的構建 “外部” DSL 的強大功能。作者研究了 Scala 的一個新的特性:case類,并重新審視一個功能強大的特性:模式匹配。

上個月的文章發表后,我又收到了一些抱怨/評論,說我迄今為止在本系列中所用的示例都沒涉及到什么實質性的問題。當然在學習一個新語言的初期使用一些小例子是很合理的,而讀者想要看到一些更 “現實的” 示例,從而了解語言的深層領域和強大功能以及其優勢,這也是理所當然的。因此,在這個月的文章中,我們來分兩部分練習構建特定于領域的語言(DSL)— 本文以一個小的計算器語言為例。

特定于領域的語言

可能您無法(或沒有時間)承受來自于您的項目經理給您的壓力,那么讓我直接了當地說吧:特定于領域的語言無非就是嘗試(再一次)將一個應用程序的功能放在它該屬于的地方 — 用戶的手中。

通過定義一個新的用戶可以理解并直接使用的文本語言,程序員成功擺脫了不停地處理 UI 請求和功能增強的麻煩,而且這樣還可以使用戶能夠自己創建腳本以及其他的工具,用來給他們所構建的應用程序創建新的行為。雖然這個例子可能有點冒險(或許會惹來幾封抱怨的電子郵件),但我還是要說,DSL 的最成功的例子就是 Microsoft® Office Excel “語言”,用于表達電子表格單元格的各種計算和內容。甚至有些人認為 SQL 本身就是 DSL,但這次是一個旨在與關系數據庫相交互的語言(想象一下如果程序員要通過傳統 API read()/write() 調用來從 Oracle 中獲取數據的話,那將會是什么樣子)。

這里構建的 DSL 是一個簡單的計算器語言,用于獲取并計算數學表達式。其實,這里的目標是要創建一個小型語言,這個語言能夠允許用戶來輸入相對簡單的代數表達式,然后這個代碼來為它求值并產生結果。為了盡量簡單明了,該語言不會支持很多功能完善的計算器所支持的特性,但我不也不想把它的用途限定在教學上 — 該語言一定要具備足夠的可擴展性,以使讀者無需徹底改變該語言就能夠將它用作一個功能更強大的語言的核心。這意味著該語言一定要可以被輕易地擴展,并要盡量保持封裝性,用起來不會有任何的阻礙。

換句話說,(最終的)目標是要允許客戶機編寫代碼,以達到如下的目的:

清單 1. 計算器 DSL:目標

  1. // This is Java using the Calculator  
  2. String s = "((5 * 10) + 7)";  
  3. double result = com.tedneward.calcdsl.Calculator.evaluate(s);  
  4. System.out.println("We got " + result); // Should be 57 

我們不會在一篇文章完成所有的論述,但是我們在本篇文章中可以學習到一部分內容,在下一篇文章完成全部內容。

從實現和設計的角度看,可以從構建一個基于字符串的解析器來著手構建某種可以 “挑選每個字符并動態計算” 的解析器,這的確***誘惑力,但是這只適用于較簡單的語言,而且其擴展性不是很好。如果語言的目標是實現簡單的擴展性,那么在深入研究實現之前,讓我們先花點時間想一想如何設計語言。

根據那些基本的編譯理論中最精華的部分,您可以得知一個語言處理器(包括解釋器和編譯器)的基本運算至少由兩個階段組成:

解析器,用于獲取輸入的文本并將其轉換成 Abstract Syntax Tree(AST)。

代碼生成器(在編譯器的情況下),用于獲取 AST 并從中生成所需字節碼;或是求值器(在解釋器的情況下),用于獲取 AST 并計算它在 AST 里面所發現的內容。
擁有 AST 就能夠在某種程度上優化結果樹,如果意識到這一點的話,那么上述區別的原因就變得更加顯而易見了;對于計算器,我們可能要仔細檢查表達式,找出可以截去表達式的整個片段的位置,諸如在乘法表達式中運算數為 “0” 的位置(它表明無論其他運算數是多少,運算結果都會是 “0”)。

您要做的***件事是為計算器語言定義該 AST。幸運的是,Scala 有 case 類:一種提供了豐富數據、使用了非常薄的封裝的類,它們所具有的一些特性使它們很適合構建 AST。

關于 DSL 的更多信息

DSL 這個主題的涉及面很廣;它的豐富性和廣泛性不是本文的一個段落可以描述得了的。想要了解更多 DSL 信息的讀者可以查閱本文末尾列出的 Martin Fowler 的 “正在進展中的圖書”;特別要注意關于 “內部” 和 “外部” DSL 之間的討論。Scala 以其靈活的語法和強大的功能而成為***有力的構建內部和外部 DSL 的語言。更加具體的介紹,可參考51CTO之前發布的DSL領域特定語言初探一文。

#p#

case類

在深入到 AST 定義之前,讓我先簡要概述一下什么是 case類。case類是使 scala 程序員得以使用某些假設的默認值來創建一個類的一種便捷機制。例如,當編寫如下內容時:

清單 2. 對 person 使用 case類

  1. case class Person(first:String, last:String, age:Int)  
  2. {  
  3.  

Scala 編譯器不僅僅可以按照我們對它的期望生成預期的構造函數 — Scala 編譯器還可以生成常規意義上的 equals()、toString() 和 hashCode() 實現。事實上,這種 case類很普通(即它沒有其他的成員),因此 case 類聲明后面的大括號的內容是可選的:

清單 3. 世界上最短的類清單

  1. case class Person(first:String, last:String, age:Int) 

這一點通過我們的老朋友 javap 很容易得以驗證:

清單 4. 神圣的代碼生成器,Batman!

  1. C:\Projects\Exploration\Scala>javap Person  
  2. Compiled from "case.scala" 
  3. public class Person extends java.lang.Object implements scala.ScalaObject,scala.  
  4. Product,java.io.Serializable{  
  5.     public Person(java.lang.String, java.lang.String, int);  
  6.     public java.lang.Object productElement(int);  
  7.     public int productArity();  
  8.     public java.lang.String productPrefix();  
  9.     public boolean equals(java.lang.Object);  
  10.     public java.lang.String toString();  
  11.     public int hashCode();  
  12.     public int $tag();  
  13.     public int age();  
  14.     public java.lang.String last();  
  15.     public java.lang.String first();  

如您所見,伴隨 case 類發生了很多傳統類通常不會引發的事情。這是因為 case 類是要與 Scala 的模式匹配(在 “集合類型” 中曾簡短分析過)結合使用的。

使用 case 類與使用傳統類有些不同,這是因為通常它們都不是通過傳統的 “new” 語法構造而成的;事實上,它們通常是通過一種名稱與類相同的工廠方法來創建的:

清單 5. 沒有使用 new 語法?

  1. object App  
  2. {  
  3.   def main(args : Array[String]) : Unit =  
  4.   {  
  5.     val ted = Person("Ted""Neward"37)  
  6.   }  

case 類本身可能并不比傳統類有趣,或者有多么的與眾不同,但是在使用它們時會有一個很重要的差別。與引用等式相比,case 類生成的代碼更喜歡按位(bitwise)等式,因此下面的代碼對 Java 程序員來說有些有趣的驚喜:

清單 6. 這不是以前的類

  1. object App  
  2. {  
  3.   def main(args : Array[String]) : Unit =  
  4.   {  
  5.     val ted = Person("Ted""Neward"37)  
  6.     val ted2 = Person("Ted""Neward"37)  
  7.     val amanda = Person("Amanda""Laucher"27)  
  8.  
  9.     System.out.println("ted == amanda: " +  
  10.       (if (ted == amanda) "Yes" else "No"))  
  11.     System.out.println("ted == ted: " +  
  12.       (if (ted == ted) "Yes" else "No"))  
  13.     System.out.println("ted == ted2: " +  
  14.       (if (ted == ted2) "Yes" else "No"))  
  15.   }  
  16. }  
  17.  
  18. /*  
  19. C:\Projects\Exploration\Scala>scala App  
  20. ted == amanda: No  
  21. ted == ted: Yes  
  22. ted == ted2: Yes  
  23.  */ 

case 類的真正價值體現在模式匹配中,本系列的讀者可以回顧一下模式匹配(參見 本系列的第二篇文章,關于 Scala 中的各種控制構造),模式匹配類似 Java 的 “switch/case”,只不過它的本領和功能更加強大。模式匹配不僅能夠檢查匹配構造的值,從而執行值匹配,還可以針對局部通配符(類似局部 “默認值” 的東西)匹配值,case 還可以包括對測試匹配的保護,來自匹配標準的值還可以綁定于局部變量,甚至符合匹配標準的類型本身也可以進行匹配。

有了 case 類,模式匹配具備了更強大的功能,如清單 7 所示:

清單 7. 這也不是以前的 switch

  1. case class Person(first:String, last:String, age:Int);  
  2.  
  3. object App  
  4. {  
  5.   def main(args : Array[String]) : Unit =  
  6.   {  
  7.     val ted = Person("Ted""Neward"37)  
  8.     val amanda = Person("Amanda""Laucher"27)  
  9.  
  10.     System.out.println(process(ted))  
  11.     System.out.println(process(amanda))  
  12.   }  
  13.   def process(p : Person) =  
  14.   {  
  15.     "Processing " + p + " reveals that" +  
  16.     (p match  
  17.     {  
  18.       case Person(_, _, a) if a > 30 =>  
  19.         " they're certainly old." 
  20.       case Person(_, "Neward", _) =>  
  21.         " they come from good genes...." 
  22.       case Person(first, last, ageInYears) if ageInYears > 17 =>  
  23.         first + " " + last + " is " + ageInYears + " years old." 
  24.       case _ =>   
  25.         " I have no idea what to do with this person" 
  26.     })  
  27.   }  
  28. }  
  29.  
  30. /*  
  31. C:\Projects\Exploration\Scala>scala App  
  32. Processing Person(Ted,Neward,37) reveals that they're certainly old.  
  33. Processing Person(Amanda,Laucher,27) reveals that Amanda Laucher is 27 years old  
  34. .  
  35.  */ 

清單 7 中發生了很多操作。下面就讓我們先慢慢了解發生了什么,然后回到計算器,看看如何應用它們。

首先,整個 match 表達式被包裹在圓括號中:這并非模式匹配語法的要求,但之所以會這樣是因為我把模式匹配表達式的結果根據其前面的前綴串聯了起來(切記,函數性語言里面的任何東西都是一個表達式)。

其次,***個 case 表達式里面有兩個通配符(帶下劃線的字符就是通配符),這意味著該匹配將會為符合匹配的 Person 中那兩個字段獲取任何值,但是它引入了一個局部變量 a,p.age 中的值會綁定在這個局部變量上。這個 case 只有在同時提供的起保護作用的表達式(跟在它后邊的 if 表達式)成功時才會成功,但只有***個 Person 會這樣,第二個就不會了。第二個 case 表達式在 Person 的 firstName 部分使用了一個通配符,但在 lastName 部分使用常量字符串 Neward 來匹配,在 age 部分使用通配符來匹配。

由于***個 Person 已經通過前面的 case 匹配了,而且第二個 Person 沒有姓 Neward,所以該匹配不會為任何一個 Person 而被觸發(但是,Person("Michael", "Neward", 15) 會由于***個 case 中的 guard 子句失敗而轉到第二個 case)。

第三個示例展示了模式匹配的一個常見用途,有時稱之為提取,在這個提取過程中,匹配對象 p 中的值為了能夠在 case 塊內使用而被提取到局部變量中(***個、***一個和 ageInYears)。***的 case 表達式是普通 case 的默認值,它只有在其他 case 表達式均未成功的情況下才會被觸發。

簡要了解了 case類和模式匹配之后,接下來讓我們回到創建計算器 AST 的任務上。

#p#

計算器 AST

首先,計算器的 AST 一定要有一個公用基類型,因為數學表達式通常都由子表達式組成;通過 “5 + (2 * 10)” 就可以很容易地看到這一點,在這個例子中,子表達式 “(2 * 10)” 將會是 “+” 運算的右側運算數。

事實上,這個表達式提供了三種 AST 類型:

◆基表達式

◆承載常量值的 Number 類型

◆承載運算和兩個運算數的 BinaryOperator

想一下,算數中還允許將一元運算符用作求負運算符(減號),將值從正數轉換為負數,因此我們可以引入下列基本 AST:

清單 8. 計算器 AST(src/calc.scala)

  1. package com.tedneward.calcdsl  
  2. {  
  3.   private[calcdsl] abstract class Expr  
  4.   private[calcdsl]  case class Number(value : Double) extends Expr  
  5.   private[calcdsl]  case class UnaryOp(operator : String, arg : Expr) extends Expr  
  6.   private[calcdsl]  case class BinaryOp(operator : String, left : Expr, right : Expr)  
  7.    extends Expr  

注意包聲明將所有這些內容放在一個包(com.tedneward.calcdsl)中,以及每一個類前面的訪問修飾符聲明表明該包可以由該包中的其他成員或子包訪問。之所以要注意這個是因為需要擁有一系列可以測試這個代碼的 JUnit 測試;計算器的實際客戶機并不一定非要看到 AST。因此,要將單元測試編寫成 com.tedneward.calcdsl 的一個子包:

清單 9. 計算器測試(testsrc/calctest.scala)

  1. package com.tedneward.calcdsl.test  
  2. {  
  3.   class CalcTest  
  4.   {  
  5.     import org.junit._, Assert._  
  6.       
  7.     @Test def ASTTest =  
  8.     {  
  9.       val n1 = Number(5)  
  10.  
  11.       assertEquals(5, n1.value)  
  12.     }  
  13.       
  14.     @Test def equalityTest =  
  15.     {  
  16.       val binop = BinaryOp("+", Number(5), Number(10))  
  17.         
  18.       assertEquals(Number(5), binop.left)  
  19.       assertEquals(Number(10), binop.right)  
  20.       assertEquals("+", binop.operator)  
  21.     }  
  22.   }  
  23. }  

到目前為止還不錯。我們已經有了 AST。

再想一想,我們用了四行 Scala 代碼構建了一個類型分層結構,表示一個具有任意深度的數學表達式集合(當然這些數學表達式很簡單,但仍然很有用)。與 Scala 能夠使對象編程更簡單、更具表達力相比,這不算什么(不用擔心,真正強大的功能還在后面)。

接下來,我們需要一個求值函數,它將會獲取 AST,并求出它的數字值。有了模式匹配的強大功能,編寫這樣的函數簡直輕而易舉:

清單 10. 計算器(src/calc.scala)

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   object Calc  
  6.   {  
  7.     def evaluate(e : Expr) : Double =  
  8.     {  
  9.       e match {  
  10.         case Number(x) => x  
  11.         case UnaryOp("-", x) => -(evaluate(x))  
  12.         case BinaryOp("+", x1, x2) => (evaluate(x1) + evaluate(x2))  
  13.         case BinaryOp("-", x1, x2) => (evaluate(x1) - evaluate(x2))  
  14.         case BinaryOp("*", x1, x2) => (evaluate(x1) * evaluate(x2))  
  15.         case BinaryOp("/", x1, x2) => (evaluate(x1) / evaluate(x2))  
  16.       }  
  17.     }  
  18.   }  
  19. }  

注意 evaluate() 返回了一個 Double,它意味著模式匹配中的每一個 case 都必須被求值成一個 Double 值。這個并不難:數字僅僅返回它們的包含的值。但對于剩余的 case(有兩種運算符),我們還必須在執行必要運算(求負、加法、減法等)前計算運算數。正如常在函數性語言中所看到的,會使用到遞歸,所以我們只需要在執行整體運算前對每一個運算數調用 evaluate() 就可以了。

大多數忠實于面向對象的編程人員會認為在各種運算符本身以外 執行運算的想法根本就是錯誤的 — 這個想法顯然大大違背了封裝和多態性的原則。坦白說,這個甚至不值得討論;這很顯然違背 了封裝原則,至少在傳統意義上是這樣的。

在這里我們需要考慮的一個更大的問題是:我們到底從哪里封裝代碼?要記住 AST 類在包外是不可見的,還有就是客戶機(最終)只會傳入它們想求值的表達式的一個字符串表示。只有單元測試在直接與 AST case 類合作。

#p#

但這并不是說所有的封裝都沒有用了或過時了。事實上恰好相反:它試圖說服我們在對象領域所熟悉的方法之外,還有很多其他的設計方法也很奏效。不要忘了 Scala 兼具對象和函數性;有時候 Expr 需要在自身及其子類上附加其他行為(例如,實現良好輸出的 toString 方法),在這種情況下可以很輕松地將這些方法添加到 Expr。函數性和面向對象的結合提供了另一種選擇,無論是函數性編程人員還是對象編程人員,都不會忽略到另一半的設計方法,并且會考慮如何結合兩者來達到一些有趣的效果。

從設計的角度看,有些其他的選擇是有問題的;例如,使用字符串來承載運算符就有可能出現小的輸入錯誤,最終會導致結果不正確。在生產代碼中,可能會使用(也許必須使用)枚舉而非字符串,使用字符串的話就意味著我們可能潛在地 “開放” 了運算符,允許調用出更復雜的函數(諸如 abs、sin、cos、tan 等)乃至用戶定義的函數;這些函數是基于枚舉的方法很難支持的。

對所有設計和實現的來說,都不存在一個適當的決策方法,只能承擔后果。后果自負。

但是這里可以使用一個有趣的小技巧。某些數學表達式可以簡化,因而(潛在地)優化了表達式的求值(因此展示了 AST 的有用性):

◆任何加上 “0” 的運算數都可以被簡化成非零運算數。

◆任何乘以 “1” 的運算數都可以被簡化成非零運算數。

◆任何乘以 “0” 的運算數都可以被簡化成零。

不止這些。因此我們引入了一個在求值前執行的步驟,叫做 simplify(),使用它執行這些具體的簡化工作:

清單 11. 計算器(src/calc.scala)

  1. def simplify(e : Expr) : Expr =  
  2. {  
  3.   e match {  
  4.     // Double negation returns the original value  
  5.     case UnaryOp("-", UnaryOp("-", x)) => x  
  6.     // Positive returns the original value  
  7.     case UnaryOp("+", x) => x  
  8.     // Multiplying x by 1 returns the original value  
  9.     case BinaryOp("*", x, Number(1)) => x  
  10.     // Multiplying 1 by x returns the original value  
  11.     case BinaryOp("*", Number(1), x) => x  
  12.     // Multiplying x by 0 returns zero  
  13.     case BinaryOp("*", x, Number(0)) => Number(0)  
  14.     // Multiplying 0 by x returns zero  
  15.     case BinaryOp("*", Number(0), x) => Number(0)  
  16.     // Dividing x by 1 returns the original value  
  17.     case BinaryOp("/", x, Number(1)) => x  
  18.     // Adding x to 0 returns the original value  
  19.     case BinaryOp("+", x, Number(0)) => x  
  20.     // Adding 0 to x returns the original value  
  21.     case BinaryOp("+", Number(0), x) => x  
  22.     // Anything else cannot (yet) be simplified  
  23.     case _ => e  
  24.   }  

還是要注意如何使用模式匹配的常量匹配和變量綁定特性,從而使得編寫這些表達式可以易如反掌。對 evaluate() 惟一一個更改的地方就是包含了在求值前先簡化的調用:

清單 12. 計算器(src/calc.scala)

  1. def evaluate(e : Expr) : Double =  
  2. {  
  3.   simplify(e) match {  
  4.     case Number(x) => x  
  5.     case UnaryOp("-", x) => -(evaluate(x))  
  6.     case BinaryOp("+", x1, x2) => (evaluate(x1) + evaluate(x2))  
  7.     case BinaryOp("-", x1, x2) => (evaluate(x1) - evaluate(x2))  
  8.     case BinaryOp("*", x1, x2) => (evaluate(x1) * evaluate(x2))  
  9.     case BinaryOp("/", x1, x2) => (evaluate(x1) / evaluate(x2))  
  10.   }  

還可以再進一步簡化;注意一下:它是如何實現只簡化樹的***層的?如果我們有一個包含 BinaryOp("*", Number(0), Number(5)) 和 Number(5) 的 BinaryOp 的話,那么內部的 BinaryOp 就可以被簡化成 Number(0),但外部的 BinaryOp 也會如此,這是因為此時外部 BinaryOp 的其中一個運算數是零。

我突然犯了作家的職業病了,所以我想將它留予讀者來定義。其實是想增加點趣味性罷了。如果讀者愿意將他們的實現發給我的話,我將會把它放在下一篇文章的代碼分析中。將會有兩個測試單元來測試這種情況,并會立刻失敗。您的任務(如果您選擇接受它的話)是使這些測試 — 以及其他任何測試,只要該測試采取了任意程度的 BinaryOp 和 UnaryOp 嵌套 — 通過。

結束語

顯然我還沒有說完;還有分析的工作要做,但是計算器 AST 已經成形。我們無需作出大的變動就可以添加其他的運算,運行 AST 也無需大量的代碼(按照 Gang of Four 的 Visitor 模式),而且我們已經有了一些執行計算本身的工作代碼(如果客戶機愿意為我們構建用于求值的代碼的話)。

更重要的是,您已經看到了 case 類是如何與模式匹配合作,使得創建 AST 并對其求值變得輕而易舉。這是 Scala 代碼(以及大多數函數性語言)很常用的設計,而且如果您準備認真地研究這個環境的話,這是您應當掌握的內容之一。

【相關閱讀】

  1. Scala編程語言專題
  2. 面向Java開發人員的Scala指南:包和訪問修飾符
  3. 面向Java開發人員的Scala指南:使用元組、數組和列表
  4. 面向Java開發人員的Scala指南:當繼承中的對象遇到函數
  5. 面向Java開發人員的Scala指南:使用Scala版本的Java接口
責任編輯:yangsai 來源: IBMDW
相關推薦

2009-06-19 13:16:36

Scala計算器解析器組合子

2009-06-19 11:42:09

Scala計算器解析

2009-06-16 17:54:38

Scala類語法語義

2009-09-09 11:37:08

Scala的模式匹配

2009-09-28 11:01:39

從Java走進Scal

2009-08-21 16:17:25

ScalaTwitter API

2009-06-17 13:57:25

Scala元組數組

2009-06-17 11:44:22

Scala控制結構

2009-06-19 10:51:39

Scalapackage訪問修飾符

2009-07-15 10:14:25

Scala并發性

2009-12-09 09:15:47

從Java走進ScalTwitter API

2009-02-04 17:32:03

ibmdwJavaScala

2009-10-14 11:14:38

ScitterScalaTwitter

2009-07-08 15:35:18

Case類Scala

2009-06-16 17:09:17

Scala面向對象函數編程

2009-08-14 11:35:01

Scala Actor

2009-06-17 13:26:06

scala繼承模型

2011-09-16 14:13:15

Windows7計算器

2022-09-09 00:25:48

Python工具安全

2022-09-08 11:35:45

Python表達式函數
點贊
收藏

51CTO技術棧公眾號

国产精品污网站| 欧美1区3d| 欧美日韩国产一二三| av动漫免费观看| 亚洲精品成人电影| 久久精品电影| 欧美成人免费一级人片100| 69xxx免费视频| 国产一区二区三区影视| 亚洲美女在线一区| 免费精品视频一区二区三区| 在线观看免费视频a| 欧美色图首页| 在线看福利67194| 一级黄色免费视频| 456成人影院在线观看| 亚洲综合免费观看高清完整版 | 国产乱子夫妻xx黑人xyx真爽| 五月天婷婷综合网| 秋霞欧美视频| 亚洲精品ady| 亚洲一二三不卡| 日本韩国欧美| 亚洲一区二区三区国产| 天堂√在线观看一区二区| 好男人在线视频www| 另类中文字幕网| 欧洲中文字幕国产精品| 久久一级黄色片| 日韩激情图片| 亚洲人成五月天| 性欧美18—19sex性高清| 日韩综合久久| 在线观看亚洲精品| 久在线观看视频| 丝袜中文在线| 亚洲日本va在线观看| 日本一区精品| 日韩欧美在线番号| 中国极品少妇xxxx| av资源种子在线观看| 成人免费看视频| 亚洲一区美女视频在线观看免费| 久久久久久久久久一级| 国产精品五区| 97视频在线免费观看| 黄色一级片在线| 婷婷综合视频| 久久亚洲综合国产精品99麻豆精品福利| 波多野结衣福利| 人人精品亚洲| 日韩国产欧美精品在线| 亚洲色图14p| 精品中国亚洲| 日韩精品在线第一页| 日韩av无码一区二区三区不卡 | 成人一区二区电影| 亚洲在线观看av| 久久se精品一区二区| 国产精品一区二区电影| 中文字幕一区二区三区四区视频 | 国内精品久久久久伊人av| 久草福利资源在线| 爽成人777777婷婷| 精品国模在线视频| 日韩成人短视频| 女生裸体视频一区二区三区| 久色乳综合思思在线视频| 国产黄在线免费观看| 91精品国产调教在线观看| 久久亚洲综合国产精品99麻豆精品福利 | 久久精品无码人妻| 亚洲激情亚洲| 欧美有码在线视频| 中文字幕 自拍偷拍| 麻豆视频观看网址久久| 91网站在线看| 亚洲精品成av人片天堂无码| 91香蕉视频mp4| 日本成人三级电影网站| 日韩在线资源| 一区二区三区av电影 | 色婷婷综合久色| 熟女人妇 成熟妇女系列视频| jizzyou欧美16| 91精品国产综合久久久久久| 伊人av在线播放| 日韩有码一区| 日韩综合中文字幕| 日韩精品久久久久久久| 日本aⅴ精品一区二区三区| 成人啪啪免费看| 色wwwwww| 国产精品盗摄一区二区三区| 国产精品久久久久7777| 久久久人成影片一区二区三区在哪下载| 欧美三级蜜桃2在线观看| 成人三级做爰av| 精品在线手机视频| 乱亲女秽乱长久久久| 国产污视频在线看| 日本在线观看不卡视频| 成人国产一区二区| 二区在线视频| 亚洲一区在线观看免费| 日韩精品你懂的| 亚洲av熟女国产一区二区性色| 成人一区视频| 日韩av资源在线播放| 精品手机在线视频| 中文一区二区| 99理论电影网| 午夜在线视频播放| 福利一区福利二区微拍刺激| www.久久com| 国产亚洲欧美日韩在线观看一区二区| 欧美另类暴力丝袜| 国产精品sm调教免费专区| 成人免费视频免费观看| 中国人体摄影一区二区三区| 日韩精品极品| 亚洲精品在线免费观看视频| 国精产品久拍自产在线网站| 久久久久久9| 精品久久蜜桃| 色呦呦在线视频| 欧美人伦禁忌dvd放荡欲情| 中国黄色a级片| 禁久久精品乱码| 亚洲wwwav| 在线观看黄色av| 色妞www精品视频| 国产老熟女伦老熟妇露脸| 亚洲av熟女国产一区二区性色 | 亚洲第一精品夜夜躁人人爽 | 五月婷在线视频| 一区二区三区丝袜| www.偷拍.com| 中文一区一区三区免费在线观看| 国产精品日韩在线| 男男电影完整版在线观看| 午夜激情一区二区三区| 亚洲日本久久久| 狠狠噜噜久久| 国产在线精品一区| aa国产成人| 亚洲国产欧美久久| 青青草av在线播放| av在线这里只有精品| www.国产在线播放| 懂色av一区二区| 国内精品久久久久影院优 | 亚洲欧美国产制服动漫| 看片网址国产福利av中文字幕| 成人av第一页| 人人妻人人添人人爽欧美一区| 国产精品jk白丝蜜臀av小说 | 欧美××××黑人××性爽| 亚洲免费一在线| 成人免费网视频| 视频一区 中文字幕| 亚洲成人免费电影| 97人妻天天摸天天爽天天| 国产日产高清欧美一区二区三区| 久99久视频| a日韩av网址| 色吧影院999| 99久久久国产精品无码网爆| 亚洲一区影音先锋| 中文字幕第3页| 香蕉成人久久| 亚洲免费精品视频| 91成人短视频在线观看| 精品少妇一区二区30p| 亚洲高清在线观看视频| 午夜精品一区在线观看| 中文字幕狠狠干| 日本成人在线电影网| 麻豆md0077饥渴少妇| 成人直播在线观看| 日本精品中文字幕| 快射视频在线观看| 精品国产91亚洲一区二区三区婷婷| av资源免费观看| 国产精品美女久久福利网站| 中国老熟女重囗味hdxx| 国产一区二区你懂的| 神马影院一区二区| 日韩一区二区三区在线看| 97av在线视频免费播放| 超碰97在线免费观看| 欧美va天堂va视频va在线| caoporn国产| 亚洲人xxxx| 人妻无码一区二区三区| 久久av中文字幕片| 日韩国产欧美亚洲| 亚洲成人免费| 欧美人与性禽动交精品| 精品视频在线观看免费观看 | 美腿丝袜一区二区三区| 欧美国产视频一区| 精品视频免费| 国产免费一区| 亚洲最大的免费视频网站| 4444欧美成人kkkk| 中文字幕中文字幕在线十八区 | 黄色a一级视频| 极品少妇xxxx精品少妇偷拍| 大肉大捧一进一出好爽视频| 亚洲最新av| 日韩福利二区| 欧美a一欧美| 亚洲最大激情中文字幕| 成人四虎影院| 日本sm极度另类视频| 女子免费在线观看视频www| 中文字幕日韩欧美在线| 性xxxx视频| 欧美成人性战久久| 国产精品人人妻人人爽| 色94色欧美sute亚洲线路一ni| 国产亚洲欧美精品久久久久久| 国产精品福利av| 性欧美精品中出| 99久久国产免费看| 亚洲精品成人无码毛片| 精品一二三四在线| 日本超碰在线观看| 日韩国产欧美在线视频| 欧美色图另类小说| 最新亚洲视频| 久草视频国产在线| 欧美激情视频一区二区三区在线播放| 一区二区三区久久网| 青草国产精品| 视频在线99re| 精品视频97| 亚洲高清不卡一区| 激情五月综合| 色噜噜狠狠一区二区三区| 亚洲欧美成人vr| 欧美日韩在线观看一区| 亚洲人成网www| 蜜桃视频成人| 全球av集中精品导航福利| 精品国产免费人成电影在线观...| 91精品导航| 国产精品免费一区二区| 红杏aⅴ成人免费视频| 国产精品久久久久久久久久久久午夜片| 日本伊人久久| 不卡视频一区| 国产精品色呦| 久久久久高清| 免费一区二区| 日韩精品第一页| 成人看的视频| 在线无限看免费粉色视频| 五月开心六月丁香综合色啪 | 欧美综合久久久| 亚洲精品国产欧美在线观看| 欧美天天综合网| 国产乱叫456在线| 日韩欧美中文字幕制服| 午夜精品久久久久久久爽| 精品盗摄一区二区三区| 青青国产在线| 色一区av在线| 日本动漫同人动漫在线观看| 久久男人资源视频| 在线免费日韩片| 国产精品久久久久久亚洲调教 | 91成人破解版| 国产精品久久久久久久裸模| 国产va在线播放| 精品久久久久久久久久久久久久 | 欧美男男video| 91福利视频网| 日韩成人在线电影| 粉嫩高清一区二区三区精品视频| 网红女主播少妇精品视频| 亚洲bbw性色大片| 欧美午夜免费影院| 国模杨依粉嫩蝴蝶150p| 国内精品在线播放| 国产熟女高潮一区二区三区| 国产精品午夜电影| 精品少妇久久久久久888优播| 一本久道久久综合中文字幕| 国产喷水福利在线视频| 日韩精品免费观看| 嫩草香蕉在线91一二三区| 国模精品一区二区三区色天香| 欧洲av一区二区| 国产精品一区二区免费看| 日本大胆欧美| 国产原创中文在线观看| 免费看欧美美女黄的网站| av电影在线播放| 国产精品乱码一区二区三区软件 | 一级黄色片毛片| 国产网站一区二区| 久久视频免费在线观看| 欧美日韩一区二区三区免费看| 亚洲精品国产片| 色婷婷综合久久久久| 黄视频免费在线看| 91久久久国产精品| 国产精品免费99久久久| 97在线国产视频| 国产乱理伦片在线观看夜一区| 中文字幕xxx| 亚洲国产一二三| 国产免费黄色录像| 一区二区三区四区视频| 538在线精品| 99久久精品免费看国产四区| 欧美成免费一区二区视频| www国产黄色| 成人一区二区在线观看| 久久嫩草捆绑紧缚| 在线观看视频91| 欧美日本网站| 97超级碰碰碰久久久| 国产精品极品国产中出| 8x8ⅹ国产精品一区二区二区| 蜜桃久久久久久| 51妺嘿嘿午夜福利| 一本大道久久a久久精品综合| 免费av网站观看| 欧美高跟鞋交xxxxhd| 国内精品视频| 香蕉精品视频在线| 久久国产婷婷国产香蕉| 波多野结衣一二三四区| 91黄视频在线观看| 国产剧情在线观看| 国产成人精品在线视频| 深爱激情综合| www日韩视频| 久久久国产精品不卡| 日韩在线视频不卡| 亚洲欧美精品一区二区| 成人开心激情| 日韩美女一区| 日本网站在线观看一区二区三区| 丰满圆润老女人hd| 日韩欧美在线观看| 男女污污视频在线观看| 国产成人啪精品视频免费网| 国产精品自拍区| 天堂社区在线视频| 欧美激情一区二区三区不卡| 中文字幕在线视频第一页| 色先锋资源久久综合5566| 自拍偷拍亚洲图片| avav在线播放| 成人动漫视频在线| 五月激情六月丁香| 亚洲三级黄色在线观看| 久久影视精品| 51xx午夜影福利| 成人ar影院免费观看视频| 欧美一区二区激情视频| 亚洲欧洲黄色网| 欧美成人aaa| 激情五月六月婷婷| 成人h精品动漫一区二区三区| 性色av免费观看| 中文国产亚洲喷潮| 日韩在线精品强乱中文字幕| 成人免费视频91| 国产午夜亚洲精品午夜鲁丝片| 在线观看免费高清视频| 欧美第一黄网免费网站| 亚洲国产最新| 在线黄色免费看| 亚洲国产精品麻豆| 美国成人毛片| 亚洲va男人天堂| 国产一级片播放| 国产黄大片在线观看画质优化| 91丨porny丨户外露出| 亚洲天堂最新地址| 欧美一级在线视频| 欧产日产国产精品视频| 午夜午夜精品一区二区三区文| 国产一二精品视频| 亚洲男人的天堂在线视频| 最近2019年日本中文免费字幕| 日韩一区二区三区色| 免费看黄色一级大片| 一区二区三区久久久| 久久久久久久影视| 亚洲综合社区网| 老牛影视一区二区三区| 国产精品99久久久久久成人| 亚洲精选在线观看| 高清在线一区二区|